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

  你可以通过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. Oracle 到 MySQL 函数替换方案汇总

    常用函数和语法转换     NVL函数 Oracle语法: NVL(COUNT(*), 0) MySQL语法: IFNULL(COUNT(*), 0)   转字符串 Oracle语法: to_char ...

  2. 算法与数据结构——AVL树(平衡二叉搜索树)

    AVL树 在"二叉搜索树"章节提到,在多次插入和删除操作后,二叉搜索树可能退化为链表.在这种情况下,所有操作的时间复杂度将从O(logn)劣化为O(n). 如下图,经过两次删除节点 ...

  3. 多表查询 —— 内连接&外连接&子查询

    连接查询 内连接 1.查询语法 -- 隐式内连接 select 字段列表 from 表1, 表2... where 条件; -- 显式内连接 select 字段列表 from 表1 [INNER] j ...

  4. Vue3——Vue Router

    安装 vue-router 依赖包 npm install vue-router@4 创建 router 文件夹,然后在里面创建一个 index.ts 文件,用于定义你的路由配置 // index.t ...

  5. PasteForm最佳CRUD实践,实际案例PasteTemplate详解(一)

    本文将介绍soft.pastecode.cn出品的PasteForm,PasteForm是贴代码使用Dto思想实现的CRUD的一个组件,或者说输出一个思想! 为啥我觉得是最佳的CRUD呢?先结合你的实 ...

  6. C++中指针和数组相关的运算符优先级

    概述 本文深入介绍了与指针和数组相关的运算符优先级,利用代码示例展示了当左结合和右结合运算符同时存在时的结合方式,同时也演示了如何使用()来强制人为指定结合顺序. 指针.数组相关的运算符优先级 下表展 ...

  7. NICE与静态优先级的关系

    在Linux系统中,nice值和静态优先级用于控制进程调度的优先级,但它们的范围和含义有所不同.让我们详细解释一下两者的区别和联系. 1. Nice值 范围:nice值的范围是从 -20 到 19. ...

  8. 4.3 等比数列及其前n项和

    \(\mathbf{{\large {\color{Red} {欢迎到学科网下载资料学习}} } }\)[[高分突破系列] 高二数学下学期同步知识点剖析精品讲义! \(\mathbf{{\large ...

  9. 墨天轮访谈 | 百度云邱学达:GaiaDB如何解决云上场景的业务需求?

    分享嘉宾:邱学达 百度云原生数据库资深技术专家 整理:墨天轮社区 导读 业务上云的大背景对弹性与可靠性的要求越来越高,传统架构的单机数据库或是分片数据库已经很难支撑快速增长的业务,导致单机瓶颈.扩容缩 ...

  10. Vue3 的watch 监视属性

    1. 监听单个 watch(变量,(新值,老值)=>{}) 2. 监听多个 watch([变量1,变量2],(新值,老值)=>{}) 3. 监听对象 watch(()=>对象,(新值 ...