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

  你可以通过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. bfs与dfs ,全球变暖——蓝桥problems178

    问题描述: ....... .##.... .##.... ....##. ..####. ...###. ....... 有一张还以N*N的像素照片,"."表示海洋," ...

  2. SXYZ-12天集训

    Day 1(6月25日) 早上四点多钟起床做七点到九点四十的飞机到杭州萧山(空客330) 然后坐一小时车到绍兴一中对面的酒店. 中午曾老师请我们在酒店隔壁吃了一桌家常菜(味道可以),以此庆祝曾老师52 ...

  3. win10安装linux的gcc

    mysy2下载gcc  过程比较艰苦,2024年秋冬讲课,被linux毒打了3天 pacman -S mingw-w64-ucrt-x86_64-gcc   这个一次成功,不行继续接大招 实在不行安装 ...

  4. eBPF 概述:第 3 部分:软件开发生态

    1. 前言 在本系列的第 1 部分和第 2 部分中,我们对 eBPF 虚拟机进行了简洁的深入研究.阅读上述部分并不是理解第 3 部分的必修课,尽管很好地掌握了低级别的基础知识确实有助于更好地理解高级别 ...

  5. 2. 说一下vue2和vue3的区别 ?

    1. vue3 使用 proxy 替换Object.defineProperty 实现数据响应式 ,所以vue3 的性能得到了提升 : 2. vue3 使用组合式 API 替代了 vue2 中的选项式 ...

  6. Android复习(三)清单文件中的元素——> activity

    转自:  https://developer.android.google.cn/guide/topics/manifest/activity-element <activity> 语法: ...

  7. 云原生爱好者周刊:Chaos Mesh 升级成为 CNCF 孵化项目

    云原生一周动态要闻: Chaos Mesh 升级成为 CNCF 孵化项目 Zabbix Web Frontend 中发现安全漏洞 2021 年 Rust 调查报告出炉 Sysdig 2022 云原生安 ...

  8. 《使用Gin框架构建分布式应用》阅读笔记:p77-p87

    <用Gin框架构建分布式应用>学习第5天,p77-p87总结,总计11页. 一.技术总结 1.Go知识点 (1)context 2.on-premises software p80, A ...

  9. Redhat 7安装Oracle 11.2.0.4 RAC 数据库软件中报错:Error in invoking target 'agent nmhs' of makefile

    环境:Redhat Linux 7.6 + Oracle 11.2.0.4 RAC 现象:图像化安装数据库软件过程中的86%时,报错. 1. 具体现象 2. 定位问题 3. 解决问题 1. 具体现象 ...

  10. LeetCode题目练习记录 _数组和链表01 _20211007

    LeetCode题目练习记录 _数组和链表01 _20211007 26. 删除有序数组中的重复项 难度简单2247 给你一个有序数组 nums ,请你原地 删除重复出现的元素,使每个元素 只出现一次 ...