axios
提供 CancelToken 办法能够勾销正在发送中的接口申请。
官网提供了两种形式勾销发送,第一种形式如下:
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 { // handle error }});axios.post('/user/12345', { name: 'new name'}, { cancelToken: source.token})// cancel the request (the message parameter is optional)source.cancel('Operation canceled by the user.');
第二种形式如下:
const CancelToken = axios.CancelToken;let cancel;axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // An executor function receives a cancel function as a parameter cancel = c; })});// cancel the requestcancel();
官网实现勾销性能的文件寄存在 /lib/cancel/CancelToken.js
尽管代码不多,然而第一次看真是一头雾水,上面就来抽丝剥茧,一步步还原外面的实现逻辑。
剖析
两种形式都调用了 CancekToken
这个构造函数,咱们就先从这个构造函数开始。
剖析:
第一种形式:
CancekToken
提供一个静态方法source
,source
办法返回token
和cancel
办法
第二种形式:
CancekToken
接管一个回调函数作为参数,回调函数接管cancel
勾销办法
第二种形式更容易动手,咱们能够先实现构造函数CancekToken
,再思考第一种形式静态方法source
的实现。
简易版 axios
首先咱们写个简易版的axios,不便咱们前面的剖析和调试:
知识点:Promise、XMLHttpRequest
function axios(url,config){ return new Promise((resolve,reject)=>{ const xhr = new XMLHttpRequest(); xhr.open(config.method || "GET",url); xhr.responseType = config.responseType || "json"; xhr.onload = ()=>{ if(xhr.readyState === 4 && xhr.status === 200){ resolve(xhr.response); }else{ reject(xhr) } }; xhr.send(config.data ? JSON.stringify(config.data) : null); })}
CancelToken
第二种形式中,咱们能够看到 CancelToken
在配置参数cancelToken
中实例化:
axios.get('/user/12345', { cancelToken: new CancelToken});
所以在axios
中,咱们也会依据配置中是否蕴含cancelToken
来勾销发送:
function axios(url,config){ return new Promise((resolve,reject)=>{ const xhr = new XMLHttpRequest(); ... if(config.cancelToken){ // 如果存在 cancelToken 参数 // xhr.abort() 终止发送工作 // reject() 走reject办法 } ...
回到配置参数,CancelToken
承受一个回调函数作为参数,参数蕴含勾销的cancel
办法,咱们初始化CancelToken
办法如下:
function CancelToken(executor){ let cancel = ()=>{}; executor(cancel)}
回到官网例子,例子中参数cancel
办法被赋值给以后环境的cancel
变量,于是以后环境cancel
变量指向CancelToken
办法中的cancel
函数表达式。
let cancel;axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { cancel = c; // 指向CancelToken中的 cancel 办法 })});
接下来cancel
办法一旦被执行,就能触发申请终止。
听起来是不是很相熟!
就是公布订阅嘛!
这里官网源码奇妙的应用了Promise
链式调用的形式实现,咱们给CancelToken
办法返回一个Promise
办法:
function CancekToken(executor){ let cancel = ()=>{}; const promise = new Promise(resolve => cancel = resolve); executor(cancel); return promise;}
接下来只有用户执行cancel
办法,配置参数cancelToken
取得的Promise办法就能响应了:
let cancel;axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { cancel = c; // 指向CancelToken中的 cancel 办法 })});// 执行+ cancel("canceled request");
axios中响应cancel
(Promise)办法:
function axios(url,config){ return new Promise((resolve,reject)=>{ const xhr = new XMLHttpRequest(); ... if(config.cancelToken){+ config.cancelToken.then(reason=>{+ xhr.abort();+ reject(reason); }) } ...
关键点是把 Promise.resolve 从函数外部抽出来,奇妙的实现了异步拆散
到了这里,第二种办法的勾销性能就根本实现了。
CancekToken.source
source
作为 CancekToken 提供的静态方法,返回token
和 cancel
办法。
cancel
办法跟后面的性能是一样的,能够了解成部分环境外面申明好cancel
再抛出来。
咱们再来看看第二种形式 token
在配置中的应用:
const CancelToken = axios.CancelToken;const source = CancelToken.source();axios.get('/user/12345', { cancelToken: source.token // token 返回的是CancelToken实例})
依据后面的配置咱们能够晓得 source.token
实际上返回的是 CancelToken
实例。
理解 source
办法须要返回的对象性能后,就能够轻松实现source
办法了:
CancekToken.source = function(){ let cancel = ()=>{}; const token = new CancekToken(c=>cancel = c); return { token, cancel }}
axios.isCancel
通过上的代码咱们晓得,勾销申请会走reject
办法,在Promise中能够被catch
到,不过咱们还须要判断catch
的谬误是否来自勾销办法,这里官网提供了isCancel
办法判断:
axios.get('/user/12345', { cancelToken: source.token}).catch(function (error) { // 判断是否 勾销操作 if (axios.isCancel(error)) {}});
在js中咱们能够通过instanceof
判断是否来自某个构造函数的实例,这里新建Cancel
办法来治理勾销发送的信息:
function Cancel(reason){ this.message = reason;}
CancekToken.source
返回的cancel
办法通过函数包装,实例化一个Cancel
作为勾销参数:
CancekToken.source = function(){ - let cancel = ()=>{};- const token = new CancekToken(c=>cancel = c); + let resolve = ()=>{};+ let token = new CancekToken(c=>resolve = c); return { token,- cancel,+ cancel:(reason)=>{+ // 实例化一个小 cancel,将 reason 传入+ resolve(new Cancel(reason))+ } }}
最终Promise.catch
到的参数来自实例Cancel
,就能够很容易的判断error
是否来自Cancel
了:
function isCancel(error){ return error instanceof Cancel}// 将 `isCancel` 绑定到 axiosaxios.isCancel = isCancel
最初,官网还判断了CancelToken.prototype.throwIfRequested
,如果调用了cancel
办法,具备雷同cancelToken
配置的ajax申请也不会被发送,这里能够参考官网代码的实现。
全副代码
最初是全副代码实现:
function Cancel(reason) { this.message = reason}function CancekToken(executor) { let reason = null let resolve = null const cancel = message => { if(reason) return; reason = new Cancel(message); resolve(reason) } const promise = new Promise(r => (resolve = r)) executor(cancel) return promise}CancekToken.source = function() { let cancel = () => {} let token = new CancekToken(c => (cancel = c)) return { token, cancel }}const source = CancekToken.source()axios('/simple/get', { cancelToken: source.token}).catch(error => { if (axios.isCancel(error)) { console.log(error) }})source.cancel('canceled http request 1')let cancelaxios('/simple/get', { cancelToken: new CancekToken(c => { cancel = c })}).catch(error => { if (axios.isCancel(error)) { console.log(error) }})cancel('canceled http request 2')function axios(url, config) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest() xhr.open(config.method || 'GET', url) xhr.responseType = config.responseType || 'json' if (config.cancelToken) { config.cancelToken.then(reason => { xhr.abort() reject(reason) }) } xhr.onload = () => { if (xhr.readyState === 4 && xhr.status === 200) { resolve(xhr.response) } else { reject(xhr) } } xhr.send(config.data ? JSON.stringify(config.data) : null) })}axios.isCancel = function(error) { return error instanceof Cancel}
es6繁难版本的实现:
export class Cancel { public reason:string; constructor(reason:string){ this.reason = reason }}export function isCancel(error:any){ return error instanceof Cancel;}export class CancelToken { public resolve:any; source(){ return { token:new Promise(resolve=>{ this.resolve = resolve; }), cancel:(reason:string)=>{ this.resolve(new Cancel(reason).reason) } } }}