axios取消請求總結
我正在參加「掘金·啟航計劃」
應用場景
取消請求在前端有時候會用到,以下是兩個工作中可能會用到的場景
- tab切換時重新整理某個列表資料,如果他們共用一個變數儲存資料列表,當請求有延時,可能會導致兩個tab資料錯亂;
- 匯出檔案或下載檔案時,中途取消 。
如何取消請求
取消http請求,axios文件裡提供了兩種用法:
第一種:使用 CancelToken ``` const { CancelToken, isCanCel } = axios; const source = CancelToken.source();
axios.get('/user/12345', { cancelToken: source.token }).catch(thrown => { if (isCancel(thrown)) { // 獲取 取消請求 的相關資訊 console.log('Request canceled', thrown.message); } else { // 處理其他異常 } });
axios.post('/user/12345', { name: 'new name' }, { cancelToken: source.token })
// 取消請求。引數是可選的,引數傳遞一個取消請求的相關資訊,在 catch 鉤子函式裡能獲取到 source.cancel('Operation canceled by the user.'); ```
第二種:給建構函式 CancelToken 傳遞一個 executor 函式作為引數。這種方法的好處是,可以用同一個 cancel token 來取消多個請求
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// 引數 c 也是個函式
cancel = c;
})
});
// 取消請求,引數用法同上
cancel();
專案中用法示例
在一個真實的專案中,一般都會對axios進行二次封裝,針對請求、響應、狀態碼、code等做處理。貼一個專案裡常用的request.js: ``` import axios from 'axios' import store from '@/store' import { getToken } from '@/utils/auth'
// 建立一個 axios 例項,並改變預設配置 const service = axios.create({ baseURL: process.env.BASE_API, // api 的 base_url timeout: 5000 // request timeout })
// 請求攔截 service.interceptors.request.use( config => { // Do something before request is sent if (store.getters.token) { // 讓每個請求攜帶token-- ['X-Token']為自定義key 請根據實際情況自行修改 config.headers['X-Token'] = getToken() } return config }, error => { // Do something with request error console.log(error) // for debug Promise.reject(error) } )
// 響應攔截 service.interceptors.response.use( response => response, error => { alert(error) return Promise.reject(error) } )
export default service
對於某一個請求新增取消的功能,要在呼叫api時,加上cancelToken選項,使用時的示例:
// api.js
import request from 'request'
export function getUsers(page, options) {
return request({
url: 'api/users',
params: {
page
},
...options
})
}
// User.vue
import { CancelToken, isCancel } from 'axios'
import { getUsers } from 'api'
...
cancel: null
...
toCancel() { this.cancel('取消請求') }
getUsers(1, { cancelToken: new CancelToken(c => (this.cancel = c)) } ) .then(...) .catch(err => { if (isCancel) { console.log(err.message) } else { ... } }) ``` 以上,我們就可以順順利利地使用封裝過的axios,取消某一個請求了。其原理無非就是把cancelToken的配置項,在呼叫api時加上,然後就可以在業務程式碼取消特定請求了。
批量取消請求
在 document 裡的第二種方法已經說過:通過指定同一個cancel token來取消。但是,在上面的專案示例中,不能控制拿到相同的cancel token。我們可以換個思路:用陣列儲存每個需要取消的cancel token,然後逐一呼叫數組裡的每一項即可: ``` // User.vue import { CancelToken, isCancel } from 'axios' import { getUsers } from 'api'
...
cancel: []
...
toCancel() { while (this.cancel.length > 0) { this.cancel.pop()('取消請求') } }
getUser1(1, { cancelToken: new CancelToken(c1 => (this.cancel.push(c1))) } )
getUser2(2, { cancelTokem: new CancleTokem(c2 => (this.cancel.push(c2))) } ) ```
切換路由時,取消請求
上面講了取消一個請求及頁面內批量abort的方法,此外,還有一種需求——切換路由時,取消所有。 這裡不詳細贅述了,大概思路就是在請求攔截器裡,統一加個token,並設定全域性變數source控制一個cancel token,在路由變化時呼叫cancel方法。 ``` http.interceptors.request.use(config => { config.cancelToken = store.source.token return config }, err => { return Promise.reject(err) })
router.beforeEach((to, from, next) => { const CancelToken = axios.CancelToken store.source.cancel && store.source.cancel() store.source = CancelToken.source() next() })
// 全域性變數 store = { source: { token: null, cancel: null } } ```
取消請求的實現原理
cancelToken的source方法維護了一個物件,裡面包括了token令牌和cancel方法,token來自與建構函式CancelToken,呼叫cancel方法後,token的promise狀態為resolved,進而又呼叫了xhr的abort方法,取消請求成功。
來分析下取消請求是怎麼實現的,先從一個簡單的取消請求的例子開始:
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
axios.get('/get?name=xmz', {
cancelToken : source.token
}).then((response)=>{
console.log('response', response)
}).catch((error)=>{
if(axios.isCancel(error)){
console.log('取消請求傳遞的訊息', error.message)
}else{
console.log('error', error)
}
})
// 取消請求
source.cancel('取消請求傳遞這條訊息');
這就是一個簡單的取消請求的例子,那麼就從最開始的axios.CancelToken
來看,先去axios/lib/axios.js
檔案中。
axios.CancelToken = require('./cancel/CancelToken');
不費吹灰之力,就找到了CancelToken,在例子中我們呼叫了source方法,那麼就去axios/lib/cancel/CancelToken.js
檔案中看看這個source方法到底是幹什麼的?
CancelToken.source = function(){
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c
})
return {
token : token,
cancel : cancel
}
}
source方法很簡單,就是返回一個具有token和cancel屬性的物件,但是token和cancel都是通過CancelToken這個建構函式來的,那麼還在這個檔案中向上看,找到CancelToken函式。
function CancelToken (executor){
// ...
// 判斷executor是一個函式,不然就報錯
var resolvePromise;
this.promise = new Promise(function(resolve){
resolvePromise = resolve;
})
var token = this;
// 以上token現在有一個promise屬性,是一個未成功的promise物件;
executor(function cancel(message){
if(token.reason){
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
})
// 這個cancel函式就是 上面函式中的cancel,也就是source.cancel;
}
現在知道了source.cancel是一個函式,souce.token是一個例項化物件,暫時就知道這些,繼續看文章最開始的例子,接下來是去傳送請求了,最下面還有一行程式碼是執行souce.cancel();
souce.cancel就是用來觸發取消請求的函式。
現在再回頭來看,上面的cancel函式,cancel執行,給token加了一個reason屬性,那麼看下這個reason屬性是什麼吧,看下這個Cancel建構函式,在axios/lib/cancel/Cancel.js
檔案中
function Cancel(message){
this.message = message
}
Cancel特別簡單就是給例項化物件新增一個message屬性,所以現在token.reason是一個具有message屬性的物件了。
繼續回到cancel函式中,resolvePromise函式執行了,那麼token.promise物件,這個原本未變成,成功狀態的promise,變成了成功狀態了,並且將token.reason物件傳遞過去了。
簡單總結一下,執行取消函式,就是讓token的promise的狀態變成了成功;
好了,突然發現分析中斷了,變成成功狀態又怎樣了,怎麼取消的呢?雖然現在的同步程式碼都執行完了,但是請求還沒傳送出去呢,我們還要去看傳送請求的函式
在分析傳送請求之前,再看下最開始的例子,和最普通的傳送一個get請求還是有一點區別的,配置物件中多了,一個cancelToken的屬性,值是token,到底起了什麼作用呢,去axios/lib/adapters/xhr.js
中一探究竟(這裡只擷取其中關於cancelToken的部分)。
// 在傳送請求之前,驗證了cancelToken,看來此處就是用來取消請求的;
if(config.cancelToken){
// 具體是如何取消的,是在這個判斷內定義的;
config.cancelToken.promise.then(function(cancel){
request.abort();
reject(cancel);
request = null;
})
}
// 傳送請求
request.send(requestData);
仔細看這只是一個promise的then函式,只有在promise的狀態變成成功後才會執行,而剛才我們分析了,cancel就是讓這個promise的狀態變成成功,所以如果執行了,取消請求的函式,這個then就會執行,取消傳送請求,並且把傳送請求的promise變成reject,被axiox.get().catch()捕獲;
流程已經清楚了,最後再總結一下:
執行cancel是讓token的promise變成成功,在真正傳送請求之前,驗證token.promise的狀態是否已經變了,如果變了,就取消請求,就是這樣一個簡單的思想來進行取消請求的。