axios執行原理了解一下!

julyL發表於2018-09-28

axios 原始碼解析

axios是一個基於Promise的http請求庫,可用於瀏覽器和 Node。可以說是目前最為常用的http庫,有必要了解一下其內部的實現原理

以一個簡單示例進行說明

const axios = require('axios');
axios.defaults.baseURL = 'http://xxx.com/api';
axios.interceptors.request.use(resolveFn1, rejectFn2); // 新增請求攔截器
axios.interceptors.response.use(resolveFn2, rejectFn2); // 新增響應攔截器
axios.get('/get').then(() => {
    // 請求成功的處理
  }, () => {
    // 請求異常的處理
  }
);
複製程式碼

上述程式碼演示瞭如何發起axios請求,先從require('axios')說起。 require('axios')匯出值的來自./lib/axios.js,而./lib/axios.js匯出是內部呼叫createInstance之後的返回值。createInstance方法會返回一個axios例項(注:axios.create也可以建立axios例項,其內部也是呼叫createInstance)。我們先來看下createInstance的原始碼是如何實現的

createInstance

// ./lib/axios.js
function createInstance(defaultConfig) {
  // 根據預設設定 新建一個Axios物件
  var context = new Axios(defaultConfig);

  // axios中所有的請求[axios, axios.get, axios.post等...]內部呼叫的都是Axios.prototype.request,見[./code/Axios.js]
  // 將Axios.prototype.request的內部this繫結到新建的Axios物件上,從而形成一個axios例項
  var instance = bind(Axios.prototype.request, context);

  utils.extend(instance, Axios.prototype, context); // 將Axios.prototype屬性新增到instance上,如果屬性為函式則繫結this為context後再新增

  utils.extend(instance, context); // 將新建的Axios物件屬性新增到instance,同上

  return instance;
}
複製程式碼

首先內部會新建一個Axios物件,Axios結構函式如下

function Axios(instanceConfig) {
  this.defaults = instanceConfig;        // 一些預設設定項
  this.interceptors = {
    request: new InterceptorManager(),   // request攔截器
    response: new InterceptorManager()   // response攔截器
  };
}
複製程式碼

新建的Axios物件主要是用來掛載axios例項的一些設定(如defaults會掛載axios例項的通用設定,interceptors用於存放攔截器)

根據原始碼可知,axios例項(instance)是對Axios.prototype.request方法包裹了一層函式,主要是為將Axios.prototype.request內部的this繫結到新建的Axios物件上。然後通過 utils.extend 將內部contextAxios.prototyp的屬性新增到這個Axios.prototype.request方法上,新增上去的函式也會繫結this到新建的Axios物件上。最終的axios例項上面的方法內部的this指向的都是新建的Axios物件,從而使得不同axios例項之間隔離了作用域,可以對每個axios例項設定不同的config

為什麼不將所有方法在Axios上實現然後返回new Axios呢?

因為axios內部呼叫的都是Axios.prototype.request方法,Axios.prototype.request預設請求方法為get。為了讓開發者可以直接axios(config)就可以傳送get請求,而不需要axios.get(config)。如果直接new一個Axios物件是無法實現這種簡寫的(沒錯,就是為了少打幾個字)

實際上axios.post、axios.put等所有axios的請求方法內部都是呼叫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]);
  }
  // 進行配置項的合併  優先順序: Axios預設的defaults < Axios.defaults < 呼叫時axios請求方法時傳入的config
  config = utils.merge(defaults, {
    method: 'get'               // 預設為get方法
  }, this.defaults, config);
  config.method = config.method.toLowerCase();

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

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

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

  /*
    假如我們分別新增了2個request和respnse攔截器, 那麼最終執行順序如下:
    request.interceptor2 => request.interceptor1 => [dispatchRequest, undefined] => response.interceptor1 => response.interceptor2
    內部通過promise.then形成promise鏈, 從而將chain中攔截器的呼叫串聯起來, dispatchRequest是對於ajax請求發起的封裝實現,也會返回一個Promise物件
  */
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift());
  }

  return promise;
};
複製程式碼

Axios.protytype.request內部會進行了一些配置項的合併工作,變數chain相當於一個任務佇列,以2個為一組存放任務(1個是任務成功回撥,1個是任務失敗回撥),通過不斷呼叫promise.then方法形成一個promise鏈,從而將所有的任務執行串聯起來。

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

 執行順序示例:request.interceptor2 => request.interceptor1 => [dispatchRequest, undefined] => response.interceptor1 => response.interceptor2
複製程式碼

request.interceptor用於請求發起前的準備工作(可以修改data和headers),response.interceptor用於伺服器返回資料之後的處理工作(也是對data進行處理),整個請求過程的發起過程是通過dispatchRequest實現的

dispatchRequest

// 省略部分程式碼,詳細程式碼見./lib/code/dispatchRequest
function dispatchRequest(config) {
  // ...
  // 依次呼叫transformRequest陣列中的函式對data,headers進行處理,方便在向伺服器傳送請求之前對data和headers進行修改(例如對data進行編碼加密等)
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );
  // ...
  return adapter(config).then(
    function onAdapterResolution(response) {
      // 判斷請求是否被取消,如果請求已經被手動取消則會丟擲一個異常
      throwIfCancellationRequested(config);

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

      return response;
    },
    function onAdapterRejection(reason) {
      // 執行到這裡說明請求出現了異常(程式碼執行出錯或者狀態碼錯誤等),但是如果這是執行取消請求操作,那麼最終的異常資訊還是取消請求所丟擲的異常,這樣是為了當開發者手動取消請求時,可以對所有取消請求進行統一的後續處理
      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)是為了向伺服器傳送請前對 data 進行處理,可以通過設定transformRequest對data和header進行修改,一般進行加密編碼等操作。

adapter 是一個典型的介面卡模式的實現, 其預設值為getDefaultAdapter的返回值

// 見./lib/cord/defaults.js
// 根據當前執行環境(瀏覽器 or Node)執行相應的請求發起邏輯
function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') { // 瀏覽器環境
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined') { // node環境
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}
複製程式碼

可以看到adapter內部對不同環境做了適配處理,封裝了統一的行為: 根據config傳送請求然後返回一個promise,promise的狀態根據請求的結果來決定。 各個環境具體的實現,可自行閱讀原始碼

接下來我們來看下adapter返回promise的成功和失敗回撥是如何處理的

  1. onAdapterResolution方法
  • 呼叫throwIfCancellationRequested來判斷請求是否被取消(axios中可以通過cancelToken取消請求),如果請求已經被手動取消則會丟擲一個異常
  • 呼叫transformResponse對服務返回的資料進行處理,一般進行解密解碼等操作
  • 返回處理之後的response給開發者使用
  1. onAdapterRejection
  • 請求失敗的原因可分為2種,1種是普通的請求異常(如:後臺返回了錯誤的狀態碼、程式碼執行出錯等),另一種是我們手動取消了請求從而丟擲的異常,2者可以根據isCancel進行區分。注意:當執行過程同時出現2種異常時,axios返回的異常最終會是取消請求所拋的異常

至此 axios 庫的處理流程就結束了。

結語

本文大致講了一下axios的執行原理,還有很多細節沒有涉及,更多見原始碼註釋
由於之前沒有使用過axios╮(╯▽╰)╭ ,如有不對之處請多多指教!!!

相關文章