按照惯例,我们先来看下官方的例子

  你可以通过axios的CancelToken工厂函数,生成一个source,然后把这个对象作为参数传递给axios,最后,需要取消的时候调用source的cancel方法即可。

  你还可以通过在参数中绑定new CancelToken的参数中的回调,来赋值执行取消操作。

  最后,你还可以通过fetch的API来执行取消操作。OK,我们来看下如何实现这样的取消功能。首先,我们先在lib目录下创建一个cancel文件夹,然后在cancel下创建个Cancel.js:

// 这这个是啥呢?就是你取消请求的时候抛出去的错误类型的类
// 很简单,就是个message
function Cancel(message) {
this.message = message;
} // 把它转换成字符串的toString方法的自定义实现
Cancel.prototype.toString = function toString() {
return "Cancel" + (this.message ? ": " + this.message : "");
}; // 原型上绑定个__CANCEL__,给isCancel做判断的条件。
Cancel.prototype.__CANCEL__ = true; export default Cancel;

  代码很简单,实际上就是一个取消的消息字符串。并改写了原型上的toString方法。然后我们isCancel.js:

export default function isCancel(value) {
return !!(value && value.__CANCEL__);
}

  它是用来判断当前的请求是否已经被取消了,以便我们去做判断条件后的其他操作。关键的来了,我们来创建一个CancelToken.js:

function CancelToken(executor) {
if (typeof executor !== "function") {
throw new TypeError("executor must be a function.");
}
// 存储promise resolve方法的便利
var resolvePromise;
// 给实例绑定个promise,并把resolve赋值给resolvePromise
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
}); var token = this; // eslint-disable-next-line func-names
this.promise.then(function (cancel) {
if (!token._listeners) return; var i;
var l = token._listeners.length;
// 循环执行订阅的事件
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
}); // eslint-disable-next-line func-names
this.promise.then = function (onfulfilled) {
var _resolve;
// eslint-disable-next-line func-names
var promise = new Promise(function (resolve) {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled); promise.cancel = function reject() {
token.unsubscribe(_resolve);
}; return promise;
}; executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
} token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}

  我们来看下他做了啥。注意,重点难点来了,这是我觉得整个axios里最不好理解的地方(原谅我水平有限)。首先,判断下传入的参数是不是一个函数,如果不是函数就抛出一个错误。然后通过

resolvePromise变量保存一个promise的resolve方法。下面呢,重点来了,把CancelToken的实例也就是this,赋值给token变量,然后:
  this.promise.then(function (cancel) {
if (!token._listeners) return; var i;
var l = token._listeners.length;
// 循环执行订阅的事件
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
});

  这段代码,循环执行token上订阅的事件,执行后置为null。要注意,这里的this.promise.then中的then方法,在现在这个阶段,跟promise一点关系都没有,你就把这个then方法,当成一个函数,传了一个回调函数而已。OK,先记住这个,我们继续往下:

  this.promise.then = function(onfulfilled) {
var _resolve;
// eslint-disable-next-line func-names
var promise = new Promise(function(resolve) {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled); promise.cancel = function reject() {
token.unsubscribe(_resolve);
}; return promise;
};

  看到了吧,这个就是我上面说的自定义的promise.then方法。他传入的这个形参onfuifilled就是:

function (cancel) {
if (!token._listeners) return; var i;
var l = token._listeners.length;
// 循环执行订阅的事件
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
}

  对吧?理解了没?然后我们再看this.promise.then = “函数”的这个“函数”干了啥,这就比较好理解了,同样了,声明了个变量,声明了一个该函数作用域内的真正的promise,然后promise内的同步代码中,让this也就是token,记得之前最开始的地方,把this赋给了token,这里的token就是cancelToken的实例,执行订阅的subscribe方法,订阅这个resolve,然后,在resolve的时候执行

onfulfilled方法,也就是订阅的内容。再然后,在promise变量上添加一个cancel方法,用来取消订阅。
  OK,我们再来捋一下整个CancelToken类的执行过程。

  再然后,咱们回到xhr文件中,添加些相关代码:

  1. 给实例this绑定一个真正的promise对象,把这个对象的resolve执行函数存储给resolvePromise变量,以便在恰当的时候调用。
  2. 自定义一个then方法,这个方法返回一个promise,并把这个promise的resolve状态订阅到listener中,并在promise上添加一个cancel取消订阅的方法。
  3. 然后等待外部调用

  再往下,我们看下:

CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};

  CancelToken的静态类上绑定了这样的方法。他这里的c其实就是CancelToken中executor的回调函数cancel:

executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
} token.reason = new Cancel(message);
resolvePromise(token.reason);
});

  所以,我们会在外部调用这个cancel。回头看下我们cancel的使用方法文档。是不是就理解了?接下来我们继续:

    var onCanceled;
function done() {
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
} if (config.signal) {
config.signal.removeEventListener("abort", onCanceled);
}
}

  首先,onCanceled就是如果存在取消的cancelToken参数的话,那么就会生成一个onCanceled函数,done方法就是移除的函数。很好理解吧。然后咱们往下:

    if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function (cancel) {
if (!request) {
return;
}
reject(
!cancel || (cancel && cancel.type) ? new Cancel("canceled") : cancel
);
request.abort();
request = null;
}; config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted
? onCanceled()
: config.signal.addEventListener("abort", onCanceled);
}
}

  这一块,就是如果存在着两个参数,那么给onCanceled赋值一个函数,之前说过了,signal是啥呢,其实是类似fetch的使用方法的一个参数,最开始的例子里说过了。看一下哈,其实这块的代码很好理解,就是绑定或执行原生的取消方法嘛。最最核心的一行代码:

config.cancelToken && config.cancelToken.subscribe(onCanceled);

  我们这里订阅了也就是在这里我们会真正的把取消请求加入到订阅的列表中去。当我们调用source.cancel方法时,就会执行取消了。

  我们来回顾下,如果存在取消函数的话,还调用了cancelToken上的subscribe方法订阅了一下onCanceled函数,subscribe很简单,就是一个添加操作:

CancelToken.prototype.subscribe = function subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
} if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};

  有subscribe自然也有unsubscribe:

CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
if (!this._listeners) {
return;
}
var index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
};

  就是个移除操作。然后source静态方法是这样的:

CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel,
};
};

  就是把CancelToken的实例和取消函数返回了。所以,我们在使用的时候,可以像这样来使用:

const CancelToken = axios.CancelToken;
const source = CancelToken.source(); axios
.get("/c7/get", {
cancelToken: source.token,
})
.catch(function (e) {
if (axios.isCancel(e)) {
console.log("Request canceled", e.message);
}
}); source.cancel("Operation canceled by the user.");

  好了,到这里CancelToken的实现就完成了,详细的代码大家可以去项目里查看,这块还是稍微有点绕的,大家可以自己调试,捋一下。那么下一篇,就是最后一篇了,我们会新增一些小的功能点,十分简单。

一比一还原axios源码(七)—— 取消功能的更多相关文章

  1. 一比一还原axios源码(零)—— 概要

    从vue2版本开始,vue-resource就不再被vue所维护和支持,官方也推荐使用axios,所以,从我使用axios至今,差不多有四五年了,这四五年的时间只能算是熟练应用,很多内部的实现和原理不 ...

  2. 一比一还原axios源码(四)—— Axios类

    axios源码的分析,到目前为止,算上第0章已经四章了,但是实际上,还都没有进入axios真正的主线,我们来简单回顾下.最开始我们构建了get请求,写了重要的buildURL方法,然后我们处理请求体请 ...

  3. 一比一还原axios源码(一)—— 发起第一个请求

    上一篇文章,我们简单介绍了XMLHttpRequest及其他可以发起AJAX请求的API,那部分大家有兴趣可以自己去扩展学习.另外,简单介绍了怎么去读以及我会怎么写这个系列的文章,那么下面就开始真正的 ...

  4. 一比一还原axios源码(三)—— 错误处理

    前面的章节我们已经可以正确的处理正确的请求,并且通过处理header.body,以及加入了promise,让我们的代码更像axios了.这一章我们一起来处理ajax请求中的错误. 一.错误处理 首先我 ...

  5. 一比一还原axios源码(六)—— 配置化

    上一章我们完成了拦截器的代码实现,这一章我们来看看配置化是如何实现的.首先,按照惯例我们来看看axios的文档是怎么说的: 首先我们可以可以通过axios上的defaults属性来配置api. 我们可 ...

  6. 一比一还原axios源码(八)—— 其他功能

    到此,我们完成了axios的绝大部分的功能,接下来我们来补全一下其他的小功能. 一.withCredentials  这个参数可以可以表明是否是一个跨域的请求.那这个的使用场景是啥呢?就是我们在同域的 ...

  7. 一比一还原axios源码(二)—— 请求响应处理

    上一章,我们开发了一些简单的代码,这部分代码最最核心的一个方法就是buildURL,应对了把对象处理成query参数的方方面面.虽然我们现在可以发起简单的请求了,但是第一,我们无法接收到服务器的响应, ...

  8. 一比一还原axios源码(五)—— 拦截器

    上一篇,我们扩展了Axios,构建了一个Axios类,然后通过这个Axios工厂类,创建真正的axios实例.那么今天,我们来实现下Axios的拦截器也就是interceptors.我们来简单看下Ax ...

  9. Axios源码深度剖析 - 替代$.ajax,成为xhr的新霸主

    前戏 在正式开始axios讲解前,让我们先想想,如何对现有的$.ajax进行简单的封装,就可以直接使用原声Promise了? let axios = function(config){ return ...

  10. Axios源码分析

    Axios是一个基于promise的HTTP库,可以用在浏览器和node.js中. 文档地址:https://github.com/axios/axios axios理解和使用 1.请求配置 { // ...

随机推荐

  1. SEO – 大杂烩

    参考: Google SEO 官网 Docs Zac 大神博客 Globalization 国际化语言 The Ultimate Guide to Multilingual and Multiregi ...

  2. /proc/pagetypeinfo

    这个文件是将buddyinfo的内容进一步细分: Free pages count per migrate type at order -- 不同order 按照migrate type的空闲page ...

  3. 更强的RAG:向量数据库和知识图谱的结合

    传统 RAG 的局限性 经典的 RAG 架构以向量数据库(VectorDB)为核心来检索语义相似性上下文,让大语言模型(LLM)不需要重新训练就能够获取最新的知识,其工作流如下图所示: 这一架构目前广 ...

  4. 神经网络之卷积篇:详解残差网络(ResNets)(Residual Networks (ResNets))

    详解残差网络 ResNets是由残差块(Residual block)构建的,首先解释一下什么是残差块. 这是一个两层神经网络,在\(L\)层进行激活,得到\(a^{\left\lbrack l + ...

  5. C# 并发控制框架:单线程环境下实现每秒百万级调度

    前言 在工业自动化和机器视觉领域,对实时性.可靠性和效率的要求越来越高.为了满足这些需求,我们开发了一款专为工业自动化运动控制和机器视觉流程开发设计的 C# 并发流程控制框架. 该框架不仅适用于各种工 ...

  6. Mysql中常用函数 分组,连接查询

    函数 在Mysql中函数是一组预定义的指令,用于执行特定的操作并返回结果,可类比Java中的方法.在SQL中函数根据其作用范围和返回结果方法分为两大类:单行函数,分组函数 单行函数 单行函数的特点为对 ...

  7. 怎样替换 rhel 7.3 的 yum

    [背景] 想在自己安装的虚拟机上搭建一套 git+gitee+vscode 环境(欢迎看下期文章),发现python版本是2.7,这个版本太老 想通过yum进行更新,结果提示需要注册,索性就查查替换y ...

  8. QT生成固定长度的随机字符串

    最近项目中有一个需要使用QT生成固定长度随机字符串的需求,需求也很简单,就是生成一个n位的仅包含0-9以及大写字母的字符串,因为这也是第一次使用QT自身的随机数,这里就做一下简单记录. 废话不多说,直 ...

  9. 基于Java+SpringBoot+Mysql实现的古诗词平台功能设计与实现三

    一.前言介绍: 1.1 项目摘要 随着信息技术的迅猛发展和数字化时代的到来,传统文化与现代科技的融合已成为一种趋势.古诗词作为中华民族的文化瑰宝,具有深厚的历史底蕴和独特的艺术魅力.然而,在现代社会中 ...

  10. Swagger注解中带有“/”导致SwaggerUI显示异常

    日常开发中一直使用swagger作为接口文档工具使用,这次在使用过程中发现一个问题. 正常情况下显示如下图 代码配置如下图 Controller Model 如果在Swagger注解中使用" ...