看axios文檔的時候發現cancelToken這個東東,這個是用來取消ajax請求的,一般原生的話用的是abort()這個方法。看到這玩意的第一感覺是用起來有點麻煩,但是看了內部實現,發現還是比較有意思的,今天就來分享一下。
基本使用
我們先來看看基本用法:
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/user/12345', {//get請求在第二個參數
cancelToken: source.token
}).catch(function(thrown) {
});
axios.post('/user/12345', {//post請求在第三個參數
name: 'new name'
}, {
cancelToken: source.token
});
source.cancel('不想請求了');
注意,get請求的時候,cancelToken是放在第二個參數里;post的時候,cancelToken是放在第三個參數里。
我們可以發現,它要先引用axios.CancelToken,然后調用source()方法,會產生一個token和cancel,它的內部到底如何實現,這樣做的目的是什么?
源碼分析
現在我們來看看cancelToken的源碼:
'use strict';
var Cancel = require('./Cancel');
/**
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*
* @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) {
resolvePromise = resolve;
});
var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
/**
* Throws a `Cancel` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
/**
* Returns an object that contains a new `CancelToken` and a function that, when called,
* cancels the `CancelToken`.
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
module.exports = CancelToken;
通過源碼我們可以發現,CancelToken這個類初始化的時候需要傳遞一個方法executor,並且它的內部新建了一個promise,最關鍵的是,它把promise的resolve方法控制權放在了executor方法里面!這種操作代表什么意思?我們看一個小例子:
let resolveHandle;
new Promise((resolve)=>{
resolveHandle=resolve;
}).then((val)=>{
console.log('resolve',val);
});
resolveHandle('ok');
上面的例子中,我們用resolveHandle獲取了一個promise的resolve方法的控制權,這樣,我們就可以在外部控制這個promise的成功了。要知道new Promise返回的對象是無法從外部決定它成功還是失敗的。
現在來看看source這個方法,我們可以看到,它new了一個CancelToken對象,並傳了一個方法executor;采用相同的手法,用cancel變量將executor方法的變量c的控制權拿出來了,那么這個變量c又代表啥呢?
變量c正是我們前面說到的在CancelToken初始化時,傳入executor方法的,也即:
function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
}
也就是說cancel代表的是上面的這個方法,有了這個方法,就可以在外部控制CancelToken內部的promise對象了。
在source方法中,除了cancel,還有一個token,這個token是CancelToken類的一個實例,可以訪問到內部的promise。
因此CancelToken類如此封裝的主要目的就是為了能夠分離promise和resolve方法,讓用戶可以自己調用resolve方法。一旦resolve后,就會觸發promise的then方法,現在看看內部promise后的then方法是什么:
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
上面是xhr.js的關於cancelToken部分相關代碼,可以看到,當用戶調用cancel后,就會立即執行abort方法取消請求,同時調用reject讓外層的promise失敗。