序言
我们都知道axios里面有个取消请求的功能,但是看他文档的用例会不理解为什么要这样写,也不知道具体作用是什么,今天就来探讨一下他的执行过程,知道内部怎么实现的就不怕不会用了
用法
先来cv一下文档的用例,这里简单说一下执行过程,如果在配置里面添加了cancelToken属性就会把它里面的取消请求的方法push到_listeners队列里面,然后触发source.cancel方法就会循环队列全部中断
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
简单分析源码执行过程
从入口开始axios有这样一段代码,暴露了CancelToken方法
// lib/axios.js
axios.CancelToken = require('./cancel/CancelToken') //返回的是CancelToken的构造函数
我们来看看内部结构:(看中文注释就好啦)
- 第一步 通过调用
source返回一个对象,该对象包含一个新的'CancelToken'和一个实例化后CancelToken函数里面的方法 - 第二步 new 实例通过回调函数把 cancel方法返回出去
- 第三步 看配置里面有没有
config.cancelToken - 第四步 执行
source.cancel()触发回调,最终使用的是request.abort()中断请求
// lib/adapters/xhr.js
// ...省略代码
// 如果配置里有 cancelToken 进行取消请求
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function(cancel) {
// cancel = token.reason
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);
}
}
如果有的话执行config.cancelToken.subscribe把取消方法添加到_listeners数组里面
// ./cancel/CancelToken.js
'use strict';
var Cancel = require('./Cancel');
/**
* CancelToken是一个可以用来请求取消操作的对象
*
* @class
* @param {Function} executor The executor function.
*/
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
// resolve暴露出去
resolvePromise = resolve;
});
var token = this;
// eslint-disable-next-line func-names
// 第四步执行这段代码
this.promise.then(function(cancel) {
// cancel = token.reason
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;
};
/**
* 第二步
* new 实例通过回调函数把 cancel方法返回出去
* c=cancel方法
*/
executor(
// 第四步 执行回调
function cancel(message) {
if (token.reason) {
// 已请求取消
return;
}
/**
* 设置取消提示 内部返回这些属性的对象
* token.reason = {
* message: '',
* toString() { },
* __CANCEL__: true
* }
*/
token.reason = new Cancel(message);
// resolvePromise(token.reason)==> resolve(token.reason)
resolvePromise(token.reason);
});
}
/**
* Throws a `Cancel` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
/**
* Subscribe to the cancel signal
* 第三步执行这段代码
*/
CancelToken.prototype.subscribe = function subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
};
/**
* Unsubscribe from the cancel signal
*/
CancelToken.prototype.unsubscribe = function unsubscribe(listener) {
if (!this._listeners) {
return;
}
var index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
};
/**
* 第一步
* 返回一个对象,该对象包含一个新的'CancelToken'和一个函数,
* 当调用时取消'CancelToken'
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
// c= function cancel(message)
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;