axios 底層源碼解析

axios 源碼解析
axios是一個基于Promise的http請求庫,可用于瀏覽器和 Node。可以說是目前最為常用的http庫,有必要了解一下其內(nèi)部的實現(xiàn)原理
以一個簡單示例進行說明
 const axios = require('axios');
axios.defaults.baseURL = 'http://xxx.com/api';
axios.interceptors.request.use(resolveFn1, rejectFn2); // 添加請求攔截器
axios.interceptors.response.use(resolveFn2, rejectFn2); // 添加響應(yīng)攔截器
axios.get('/get').then(() => {
    // 請求成功的處理
  }, () => {
    // 請求異常的處理
  }
);

上述代碼演示了如何發(fā)起axios請求,先從require('axios')說起。 require('axios')導(dǎo)出值的來自./lib/axios.js,而./lib/axios.js導(dǎo)出是內(nèi)部調(diào)用createInstance之后的返回值。createInstance方法會返回一個axios實例(注:axios.create也可以創(chuàng)建axios實例,其內(nèi)部也是調(diào)用createInstance)。我們先來看下createInstance的源碼是如何實現(xiàn)的

createInstance

// ./lib/axios.js
function createInstance(defaultConfig) {
  // 根據(jù)默認設(shè)置 新建一個Axios對象
  var context = new Axios(defaultConfig);

  // axios中所有的請求[axios, axios.get, axios.post等...]內(nèi)部調(diào)用的都是Axios.prototype.request,見[./code/Axios.js]
  // 將Axios.prototype.request的內(nèi)部this綁定到新建的Axios對象上,從而形成一個axios實例
  var instance = bind(Axios.prototype.request, context);

  utils.extend(instance, Axios.prototype, context); // 將Axios.prototype屬性添加到instance上,如果屬性為函數(shù)則綁定this為context后再添加

  utils.extend(instance, context); // 將新建的Axios對象屬性添加到instance,同上

  return instance;
}

首先內(nèi)部會新建一個Axios對象,Axios結(jié)構(gòu)函數(shù)如下

function Axios(instanceConfig) {
  this.defaults = instanceConfig;        // 一些默認設(shè)置項
  this.interceptors = {
    request: new InterceptorManager(),   // request攔截器
    response: new InterceptorManager()   // response攔截器
  };
}

新建的Axios對象主要是用來掛載axios實例的一些設(shè)置(如defaults會掛載axios實例的通用設(shè)置,interceptors用于存放攔截器)

根據(jù)源碼可知,axios實例(instance)是對Axios.prototype.request方法包裹了一層函數(shù),主要是為將Axios.prototype.request內(nèi)部的this綁定到新建的Axios對象上。然后通過 utils.extend 將內(nèi)部context和Axios.prototyp的屬性添加到這個Axios.prototype.request方法上,添加上去的函數(shù)也會綁定this到新建的Axios對象上。最終的axios實例上面的方法內(nèi)部的this指向的都是新建的Axios對象,從而使得不同axios實例之間隔離了作用域,可以對每個axios實例設(shè)置不同的config

為什么不將所有方法在Axios上實現(xiàn)然后返回new Axios呢?
因為axios內(nèi)部調(diào)用的都是Axios.prototype.request方法,Axios.prototype.request默認請求方法為get。為了讓開發(fā)者可以直接axios(config)就可以發(fā)送get請求,而不需要axios.get(config)。如果直接new一個Axios對象是無法實現(xiàn)這種簡寫的(沒錯,就是為了少打幾個字)
實際上axios.post、axios.put等所有axios的請求方法內(nèi)部都是調(diào)用Axios.prototype.request

Axios.prototype.request

// 見./lib/core/Axios
Axios.prototype.request = function request(config) {
  if (typeof config === 'string') {
    config = utils.merge({
      url: arguments[0]
    }, arguments[1]);
  }
  // 進行配置項的合并  優(yōu)先級: Axios默認的defaults < Axios.defaults < 調(diào)用時axios請求方法時傳入的config
  config = utils.merge(defaults, {
    method: 'get'               // 默認為get方法
  }, this.defaults, config);
  config.method = config.method.toLowerCase();

  var chain = [dispatchRequest, undefined]; // dispatchRequest封裝了對于發(fā)起ajax的邏輯處理
  var promise = Promise.resolve(config);

  // request攔截器的執(zhí)行順序是: 先加入后執(zhí)行
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  // 而response攔截器則是: 先加入的先執(zhí)行
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  /*
    假如我們分別添加了2個request和respnse攔截器, 那么最終執(zhí)行順序如下:
    request.interceptor2 => request.interceptor1 => [dispatchRequest, undefined] => response.interceptor1 => response.interceptor2
    內(nèi)部通過promise.then形成promise鏈, 從而將chain中攔截器的調(diào)用串聯(lián)起來, dispatchRequest是對于ajax請求發(fā)起的封裝實現(xiàn),也會返回一個Promise對象
  */
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};

Axios.protytype.request內(nèi)部會進行了一些配置項的合并工作,變量chain相當(dāng)于一個任務(wù)隊列,以2個為一組存放任務(wù)(1個是任務(wù)成功回調(diào),1個是任務(wù)失敗回調(diào)),通過不斷調(diào)用promise.then方法形成一個promise鏈,從而將所有的任務(wù)執(zhí)行串聯(lián)起來。

有一點需要注意是攔截器的執(zhí)行順序,request 攔截器先加入的后執(zhí)行,response 攔截器則是先加入的先執(zhí)行。

執(zhí)行順序示例:request.interceptor2 => request.interceptor1 => [dispatchRequest, undefined] => response.interceptor1 => response.interceptor2

request.interceptor用于請求發(fā)起前的準備工作(可以修改data和headers),response.interceptor用于服務(wù)器返回數(shù)據(jù)之后的處理工作(也是對data進行處理),整個請求過程的發(fā)起過程是通過dispatchRequest實現(xiàn)的

dispatchRequest
// 省略部分代碼,詳細代碼見./lib/code/dispatchRequest
function dispatchRequest(config) {
  // ...
  // 依次調(diào)用transformRequest數(shù)組中的函數(shù)對data,headers進行處理,方便在向服務(wù)器發(fā)送請求之前對data和headers進行修改(例如對data進行編碼加密等)
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );
  // ...
  return adapter(config).then(
    function onAdapterResolution(response) {
      // 判斷請求是否被取消,如果請求已經(jīng)被手動取消則會拋出一個異常
      throwIfCancellationRequested(config);

      // Transform response data
      // 利用transformResponse對服務(wù)器返回的data進行處理
      response.data = transformData(response.data, response.headers, config.transformResponse);

      return response;
    },
    function onAdapterRejection(reason) {
      // 執(zhí)行到這里說明請求出現(xiàn)了異常(代碼執(zhí)行出錯或者狀態(tài)碼錯誤等),但是如果這是執(zhí)行取消請求操作,那么最終的異常信息還是取消請求所拋出的異常,這樣是為了當(dāng)開發(fā)者手動取消請求時,可以對所有取消請求進行統(tǒng)一的后續(xù)處理
      if (!isCancel(reason)) {
      throwIfCancellationRequested(config);  

      // Transform response data
      if (reason && reason.response) {
        reason.response.data = transformData(
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }
    
    return Promise.reject(reason);
    }
  );
}

transformData(config.data, config.headers, config.transformRequest)是為了向服務(wù)器發(fā)送請前對 data 進行處理,可以通過設(shè)置transformRequest對data和header進行修改,一般進行加密編碼等操作。

adapter 是一個典型的適配器模式的實現(xiàn), 其默認值為getDefaultAdapter的返回值

// 見./lib/cord/defaults.js
// 根據(jù)當(dāng)前執(zhí)行環(huán)境(瀏覽器 or Node)執(zhí)行相應(yīng)的請求發(fā)起邏輯
function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') { // 瀏覽器環(huán)境
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined') { // node環(huán)境
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

可以看到adapter內(nèi)部對不同環(huán)境做了適配處理,封裝了統(tǒng)一的行為: 根據(jù)config發(fā)送請求然后返回一個promise,promise的狀態(tài)根據(jù)請求的結(jié)果來決定。 各個環(huán)境具體的實現(xiàn),可自行閱讀源碼

接下來我們來看下adapter返回promise的成功和失敗回調(diào)是如何處理的

  1. onAdapterResolution方法
  • 調(diào)用throwIfCancellationRequested來判斷請求是否被取消(axios中可以通過cancelToken取消請求),如果請求已經(jīng)被手動取消則會拋出一個異常
  • 調(diào)用transformResponse對服務(wù)返回的數(shù)據(jù)進行處理,一般進行解密解碼等操作
    -返回處理之后的response給開發(fā)者使用
  1. onAdapterRejection
    -請求失敗的原因可分為2種,1種是普通的請求異常(如:后臺返回了錯誤的狀態(tài)碼、代碼執(zhí)行出錯等),另一種是我們手動取消了請求從而拋出的異常,2者可以根據(jù)isCancel進行區(qū)分。注意:當(dāng)執(zhí)行過程同時出現(xiàn)2種異常時,axios返回的異常最終會是取消請求所拋的異常

       至此 axios 庫的處理流程就結(jié)束了
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Axios是近幾年非常火的HTTP請求庫,官網(wǎng)上介紹Axios 是一個基于 promise 的 HTTP 庫,可以...
    milletmi閱讀 3,618評論 0 9
  • Axios是一個基于Promise的HTTP請求庫,可以用在瀏覽器和Node.js中。平時在Vue項目中,經(jīng)常使用...
    多啦斯基周閱讀 919評論 0 0
  • Vue項目越做越多,Axios一直作為請求發(fā)送的基礎(chǔ)工程,這里就深究一下Axios的攔截器相關(guān)的一些邏輯和對應(yīng)一個...
    RandyZhang閱讀 32,925評論 9 31
  • axios 基于 Promise 的 HTTP 請求客戶端,可同時在瀏覽器和 node.js 中使用 功能特性 在...
    Yanghc閱讀 3,746評論 0 7
  • ## 框架和庫的區(qū)別?> 框架(framework):一套完整的軟件設(shè)計架構(gòu)和**解決方案**。> > 庫(lib...
    Rui_bdad閱讀 3,153評論 1 4

友情鏈接更多精彩內(nèi)容