axios源碼分析--取消請求

使用方法

方式一ios

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
  } 
});

source.cancel('Operation canceled by the user.');
複製代碼

方式二axios

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});

cancel();
複製代碼

源碼分析

axios/lib/axios.js中定義了跟取消相關的方法promise

var axios = createInstance(defaults);
...
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
...
複製代碼

我先看CancelToken,進入到axios/lib/cancel/CancelToken.js文件bash

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(cancel方法);
  executor(function cancel(message) {
    if (token.reason) {
      // Cancellation has already been requested
      return;
    }
    //token.reason是Cancel的實例
    token.reason = new Cancel(message);
    resolvePromise(token.reason);//改變promise的狀態
  });
}

CancelToken.source = function source() {
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  return {
    token: token,  //CancelToken的實例
    cancel: cancel //function cancel(message) {...}
  };
};

CancelToken.prototype.throwIfRequested = function throwIfRequested() {
  if (this.reason) {
    throw this.reason;
  }
};
複製代碼

CancelToken構造函數,定義了一個promise,狀態一直是padding,將決定狀態的resolve方法傳遞cancel方法內部,cancel方法做爲參數傳到外部executor方法中。函數

CancelToken.source實際上new 一個CancelToken的實例,上述的cancel方法傳給cancel變量源碼分析

進入到axios/lib/cancel/Cancel.js文件ui

function Cancel(message) {
  this.message = message;
}

Cancel.prototype.toString = function toString() {
  return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;
複製代碼

Cancel構造函數,有屬性message,Cancel.prototype有屬性__CANCEL__this

進入到axios/lib/cancel/isCancel.js文件spa

function isCancel(value) {
  return !!(value && value.__CANCEL__);
};
複製代碼

思考

如何一步步實現取消的?prototype

function throwIfCancellationRequested(config) {
  //判斷是否配置有cancelToken,
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

function dispatchRequest(config) {
  throwIfCancellationRequested(config);//請求前
  ...
  return adapter(config).then(function onAdapterResolution(response) {
    throwIfCancellationRequested(config);//請求後,resolve
    ...
  }, function onAdapterRejection(reason) {
    if (!isCancel(reason)) { //請求後,reject
      throwIfCancellationRequested(config);
    ...  
 });
}
  
複製代碼

調用throwIfCancellationRequested方法,內部實現先判斷是否配置有cancelToken,則調用CancelToken.prototype.throwIfRequested,若是調用cancel操做過了確定reason存在,拋出throw reason,reason其實是Cancel的實例

axios/lib/adapters/xhr.js

function xhrAdapter(config) {
    return new Promise(function dispatchXhrRequest(resolve, reject) {
        ...
        if (config.cancelToken) {
        //請求中,監聽cancelToken中promise狀態改變
          config.cancelToken.promise.then(function onCanceled(cancel) {
            if (!request) {
              return;
            }
        
            request.abort();
            reject(cancel);
            request = null;
          });
        }
        ...
    }
}
複製代碼

總結: CancelToken.source().token實際可用理解爲new 一個promise,狀態一直是 padding。把可用改變狀態方法傳到cancel方法中。外部調用cancel實現取消請求。

cancel操做,實際上是執行resolvePromise,讓cancelToken中promise狀態變爲已完成。實際上在發送請求前、請求中,請求後resolve、請求後reject四部分,都會去判斷是否有cancel操做。

  • 發送請求前,若是有cancel操做,說明reason存在,則throw reason, reason對象(實際是Cancel的實例)
  • 請求中,若是有cancel操做,說明已經創建xhr,則要abort這個請求,因此要再xhr中有config.cancelToken.promise.then等待promise狀態改變
  • 請求後,若是有cancel操做,與發送請求前處理方式同樣
相關文章
相關標籤/搜索