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

  你可以通过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. C# – 10.0

    前言 之前写过 6.0, 7.0, 8.0, 9.0 总结. 10.0 也是有些好东西哦, 尤其是 pattern matching 的完善, 差不多是时候可以重构 if else switch 的写 ...

  2. 10 分钟快速搞懂 Lambda 表达式

    Lambda简介 Lambda表达式是Java8引入的一个重要特性,相当于一个语法糖. 语法糖(Syntactic sugar)是指在编程语言中引入的一种语法,它可以使代码更易读.更简洁,但并没有引入 ...

  3. React的useId,现在Vue3.5终于也有了!

    前言 React在很早之前的版本中加了useId,用于生成唯一ID.在Vue3.5版本中,终于也有了期待已久的useId.这篇文章来带你搞清楚useId有哪些应用场景,以及他是如何实现的. 关注公众号 ...

  4. cobalt strike安装教程

    将本地IP和密码填入:./teamserver 192.168.xx.xx 密码 启动成功

  5. 【赵渝强老师】Flink的DataSet算子

    Flink为了能够处理有边界的数据集和无边界的数据集,提供了对应的DataSet API和DataStream API.我们可以开发对应的Java程序或者Scala程序来完成相应的功能.下面举例了一些 ...

  6. USB通讯架构及数据模型

    注意: (1)一个usb设备由一个或者多个接口组成: (2)每一个接口为usb设备的一个功能,比如上面的usb设备由两个接口,一个可用于鼠标,一个可用于键盘: (3)每个接口占用usb设备的多个端口资 ...

  7. slab分配器正式被弃用,slub成为分配器唯一选择

    在使用slab分配器进行内存分配时,可能会出现以下缺点: 内存碎片化.由于slab分配器需要将内存分成大小相同的块,如果分配不均衡或者对象大小不同,就容易导致内存碎片化. 性能下降.Slab分配器将内 ...

  8. 利用csv文件信息,将图片名信息保存到csv文件当中

    我们可以利用train.csv文件信息, 再结合给定的文件路径(path)信息,可以将给定字目录下的图片名信息整合到scv文件当中. train.csv文件格式: 图片名信息: 代码如下: from ...

  9. 使用doccano标注NER数据详细教程

    使用doccano标注NER数据详细教程 说明: 首次发表日期:2024-10-12 参考资料: https://github.com/zjunlp/DeepKE/blob/main/README_T ...

  10. 基于pandas的数据清洗

    数据清洗是数据科学和数据分析中非常重要的一个步骤.它指的是在数据分析之前,对数据进行预处理,以确保数据的质量和一致性.使用Python的pandas库进行数据清洗是一种常见的做法,因为pandas提供 ...