axios 源碼系列之如何取消請求

axios 源碼系列之如何取消請求

小蚊子前端

高級前端工程師ios

咱們在先後端交互的過程當中,一般是經過請求接口來實現的,而一個頁面中的交互又很是複雜,例如須要屢次頻繁請求同一個接口,或者在接口還沒返回時就要切換路由等。這些都須要對接口請求的時機或者請求接口以後進行處理,避免一些無用的請求或者接口返回順序的差別。git

  1. 防抖:在用戶快速地交互過程當中,只使用最後一次交互產生的數據,而後再發起請求,例如頻繁的切換 tab,或者快速輸入數據等;
  2. 鎖狀態:在上一個接口沒有返回數據時,交互狀態一直處於 loading 的鎖定狀態,直到數據正確返回或者超時等異常;
  3. 取消上一個請求:在發起下一個請求前,把以前的請求取消掉;
    前兩種方式,是在發起請求前進行控制,即控制發起請求的時機,而當請求發出以後則再也不控制;而最終一種方式則是取消中斷還在路上的請求,而後再發起一個新的請求,不用管發起的時機。這幾種方式也要看業務的須要,選擇最適合的便可。

axios 源碼系列之如何取消請求

咱們在以前的如何實現 axios 的自定義適配器 adapter文章裏,略過了 axios 是如何主動取消當前請求的。今天咱們就將一下在 axios 中如何取消以前發起的請求,源碼中又是怎樣實現的。github

1. 主動取消以前發起的請求

咱們先來看下 axios 中取消請求的用法:axios

const CancelToken = axios.CancelToken;

// 返回兩個字段,{ token, cancel }
// token用於表示某個請求,是一個Promise類型
// cancel是一個方法,當被調用時,則取消token注入的那個請求
const source = CancelToken.source();

axios
    .get('/user/12345', {
        cancelToken: source.token, // 將token注入到請求中
    })
    .catch(function (thrown) {
        // 判斷是不是因主動取消致使的
        if (axios.isCancel(thrown)) {
            console.log('Request canceled', thrown.message);
        } else {
            // handle error
            console.error(thrown);
        }
    });

axios.post(
    '/user/12345',
    {
        name: 'new name',
    },
    {
        cancelToken: source.token,
    }
);

// 主動取消請求
// cancel方法會把注入的同一個token的請求方法一併取消掉
// 上面的get和post請求都會被取消掉
// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

從 demo 上來看,用法很簡單,token 和 cancel 的關係對應上便可。後端

官方例子中還有一種取消請求的方式,這個咱們放在後面講,更容易理解一些。promise

2. 源碼解析

取消請求的方法在 https://github.com/axios/axios/tree/master/lib/cancel 的目錄中,3 個文件:markdown

  • Cancel: Cancel 類,message 和CANCEL屬性,用於標識取消的某個請求;
  • isCancel: 判斷當前參數是不是 Cancel 的實例;
  • CancelToken: 主流程,建立 Cancel 實例和取消的方法;
    咱們主要來看下 CancelToken 中的整個流程。

axios 源碼系列之如何取消請求

2.1 source 方法

source 做爲取消請求的入口,咱們就先來看下 source 方法。前端工程師

// 建立token和cancel方法
CancelToken.source = function source() {
    var cancel;

    // token爲 CancelToken 的實例,包含 promise 和 reason 兩個屬性
    // 同時把 executor 中的參數給到 cancel
    // 即CancelToken有一個回調函數,而這個回調函數的參數也是一個函數
    // CancelToken怎麼執行,咱們接着看!
    var token = new CancelToken(function executor(c) {
        cancel = c;
    });
    return {
        token: token,
        cancel: cancel,
    };
};

2.2 CancelToken

CancelToken 用來取消請求,但我理解起來,思路很是的繞,咱們一點點來剖析:ide

function CancelToken(executor) {
    if (typeof executor !== 'function') {
        throw new TypeError('executor must be a function.');
    }

    // 建立一個Promise的實例,
    // 當resolvePromise執行時,this.promise變爲fulfilled狀態
    var resolvePromise;
    this.promise = new Promise(function promiseExecutor(resolve) {
        resolvePromise = resolve;
    });

    // new一個實例時,會當即執行CancelToken的回調函數executor方法
    // executor的參數也是一個函數,即上面的cancel就是當前的cancel函數體
    // 當executor的回調函數cancel執行時,會給當前CancelToken建立一個reason屬性,這個屬性是Cancel的實例
    // 並執行resolvePromise方法,將reason實例穿進去;執行後this.promise變爲fulfilled狀態
    var token = this;
    executor(function cancel(message) {
        if (token.reason) {
            // Cancellation has already been requested
            return;
        }

        token.reason = new Cancel(message);
        resolvePromise(token.reason);
    });
}

也就是說會先建立一個 CancelToken 的實例 token,同時,將 CancelToken 中回調函數的參數給到了 cancel。當 cancel 執行時,則 token 中的 promise 屬性則會從 pending 狀態變爲 fulfilled 狀態,那麼 promise 上掛載的then()方法也就能夠繼續執行了。

2.3 adapter

在調用 cancel 方法後,請求中是怎麼操做的呢?咱們看下adapter/xhr.js中的代碼:

if (config.cancelToken) {
    // config.cancelToken就是上面建立的token
    // 當token.promise變爲fulfilled狀態後,就能夠執行後續的鏈式操做
    // Handle cancellation
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }

        // 取消當前的請求
        request.abort();

        // 將Cancel的實例cancel給到reject
        reject(cancel);
        // Clean up request
        request = null;
    });
}

當咱們使用 axios 的 catch 捕獲內部拋出的異常時,就能夠經過isCancel判斷是不是因主動取消請求致使的異常:

axios
    .get('/user/12345', {
        cancelToken: source.token, // 將token注入到請求中
    })
    .catch(function (thrown) {
        // 判斷是不是因主動取消致使的
        if (axios.isCancel(thrown)) {
            console.log('Request canceled', thrown.message);
        } else {
            // handle error
            console.error(thrown);
        }
    });

如今再來看下 cancel 執行的整個流程,就會清晰流暢不少。

axios 源碼系列之如何取消請求
axios 源碼系列之如何取消請求

3. 取消請求的另外一種方式

咱們在第 1 節還留着一個問題,axios 取消請求還有另外一種方式,即直接使用 CancelToken 類。

  1. token: 咱們在上面的 source()方法中就能看到,傳給 axios 參數的 token 就是 CancenToken 的實例,這裏直接使用new CancelToken()的返回值也是能夠的;
  2. cancel: source()中的 cancel 就是 CancelToken 的回調函數 executor 的回調函數;
const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
    // CancelToken建立的
    cancelToken: new CancelToken(function executor(c) {
        // An executor function receives a cancel function as a parameter
        cancel = c;
    }),
});

// cancel the request
cancel();

其實咱們發現,source()方法,只是給咱們額外又封裝了一下,簡單的返回了 token 和 cancel,但本質仍是 CancelToken 中的東西。

axios 源碼系列之如何取消請求

4. 總結

在取消請求的過程當中,token 要和 cancel 方法保持對應關係,即都在一個對象裏;若其餘的請求也要取消時,能夠額外再生成一組 token 和 cancel。同時,這裏還用到了 Promise 的一個機制,只有在當前 Promise 變動爲 fulfilled 狀態後,才能執行後面的 then 等操做。

相關文章
相關標籤/搜索