axios取消請求總結

語言: CN / TW / HK

我正在參加「掘金·啟航計劃」

應用場景

取消請求在前端有時候會用到,以下是兩個工作中可能會用到的場景

  1. tab切換時重新整理某個列表資料,如果他們共用一個變數儲存資料列表,當請求有延時,可能會導致兩個tab資料錯亂;
  2. 匯出檔案或下載檔案時,中途取消 。

如何取消請求

取消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的狀態是否已經變了,如果變了,就取消請求,就是這樣一個簡單的思想來進行取消請求的。