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

  你可以通过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. Azure 入门系列 (外传 小知识)

    数据中心地理结构 Azure 数据中心有很多,这我们知道, 但是我们还需要知道它的结构, 不然在做 Backup, Recovery Disaster 的时候会卡卡. 参考: Region, Avai ...

  2. JavaScript习题之选择题

    console.log( (2==true)+1 )会弹出A trueB falseC 1D 2正确答案: C2 ==true为假,此时值为0 在JS中,"1555"+3的运行结果 ...

  3. Windows系统无法打开‘’网络发现‘’功能

    Windows10无法开启网络发现 解决办法: 1. services.msc 2. 开启 SSDP Discovery ,设置 启动类型为 自动 ,服务状态为 启动 Windows7 无法开启网络发 ...

  4. 【赵渝强老师】Oracle RAC集群的概念

    一.什么是Oracle RAC(Real Application Cluster)? Oracle RAC 是一个具有共享缓存架构的集群数据库,它克服了传统的无共享方法和共享磁盘方法的限制,为您的所有 ...

  5. foobar2000 v1.6.13 汉化版(更新于2022.11.22)

    foobar2000 v1.6.13 汉化版 -----------------------[软件截图]---------------------- -----------------------[软 ...

  6. php7新内容总结(随时更新)

    一.参数和返回值类型申明 可以申明的有:float,int,bool,string,interfaces,array,callable 一般模式: function sum(int ...$ints) ...

  7. 深入理解Linux进程调度(下)

    一.SMP管理 在继续讲解之前,我们先来说一下多CPU管理(这里的CPU是指逻辑CPU,在很多语境中CPU都是默认指的逻辑CPU,物理CPU要特别强调是物理CPU).最开始的时候计算机都是单CPU的, ...

  8. linux中backport printk和front printk的区别

    在Linux内核中,"backport printk"和"front printk"都是用于记录内核消息和调试信息的机制,但它们的工作方式和使用场景有一些区别. ...

  9. 2024年5月中国数据库排行榜:OP持续领跑,GoldenDB稳步上升进前八

    入夏时节,2024年5月的中国流行度排行榜排行榜如期发布.在这个骄阳似火的季节,各大数据库产品之间的竞争愈发激烈,名次间的细微变动展示了市场的动态和活力.各家厂商不断创新,通过技术升级和性能优化,力求 ...

  10. idea创建搭建项目 maven eg

    1. 创建一个空的项目 ps:作为 git 管理 ,父项目 2. 创建第一个微服务 先导入两个必要的组件 web spring web : spring cloud openfeign (用于微服务之 ...