axios 源碼解析(中) 代碼結構

来源:https://www.cnblogs.com/greatdesert/archive/2019/10/08/11606941.html
-Advertisement-
Play Games

axios現在最新的版本的是v0.19.0,本節我們來分析一下它的實現源碼,首先通過 gitHub地址獲取到它的源代碼,地址:https://github.com/axios/axios/tree/v0.19.0 下載後就可以看到axios的目錄結構,主目錄下有一個index.js文件,該文件比較簡 ...


axios現在最新的版本的是v0.19.0,本節我們來分析一下它的實現源碼,首先通過 gitHub地址獲取到它的源代碼,地址:https://github.com/axios/axios/tree/v0.19.0

下載後就可以看到axios的目錄結構,主目錄下有一個index.js文件,該文件比較簡單,內容如下:

就是去引入./lib/axios模塊而已,lib目錄內容如下:

大致文件說明如下:

index.js            ;入口文件
    ├lib                ;代碼主目錄
        ├helpers            ;定義了一些輔助函數
        ├adapters          ;原生ajax和node環境下請求的封裝
        ├cancel             ;取消請求的一些封裝
        ├core                ;請求派發、攔截器管理、數據轉換等處理
        axios.js              ;也算是入口文件吧
        default.js           ;預設配置文件
        utils.js                ;工具函數

writer by:大沙漠 QQ:22969969

./lib/axios應該也可以說是一個入口文件,主要的分支如下:

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var mergeConfig = require('./core/mergeConfig');
var defaults = require('./defaults');                            //預設配置對象

/**/

function createInstance(defaultConfig) {                        //創建一個Axios的實例 參數為:Axios的預設配置
  var context = new Axios(defaultConfig);                //創建一個./lib/core/Axios對象,作為上下文
  var instance = bind(Axios.prototype.request, context);       //創建一個instance屬性,值為bind()函數的返回值

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);          //將Axios.prototype上的方法(delete、get、head、options、post、put、patch、request)extend到instans上,通過bind進行綁定

  // Copy context to instance
  utils.extend(instance, context);                    //將context上的兩個defaults和interceptors屬性保存到utils上面,這兩個都是對象,這樣我們就可以通過axios.defaults修改配置信息,通過axios.interceptors去設置攔截器了

  return instance;                             //返回instance方法
}

// Create the default instance to be exported
var axios = createInstance(defaults);                            //創建一個預設的實例作為輸出

/**/
module.exports = axios;                                            //導出符號

// Allow use of default import syntax in TypeScript
module.exports.default = axios;                                    //預設導出符號

createInstance會創建一個./lib/core/Axios的一個對象實例,保存到局部變數context中,然後調用bind函數,將返回值保存到instance中(這就是我們調用axios()執行ajax請求時所調用的符號),bind()是一個輔助函數,如下:

module.exports = function bind(fn, thisArg) {        //以thisArg為上下文,執行fn函數
  return function wrap() {
    var args = new Array(arguments.length);                //將arguments按照順序依次保存到args裡面
    for (var i = 0; i < args.length; i++) {
      args[i] = arguments[i];
    }
    return fn.apply(thisArg, args);                        //執行fn函數,參數為thisArg為上下文,args為參數
  };
};

 該函數是一個高階函數的實現,它會以參數2作為上下文,執行參數1,也就是以context為上下文,執行Axios.prototype.request函數,Axios.prototype.request就是所有非同步請求的入口了

 我們看一下Axios.prototype.request的實現,如下:

Axios.prototype.request = function request(config) {            //派發一個請求,也是ajax請求的入口
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {                                   //如果config對象是個字元串,  ;例如:axios('/api/1.php').then(function(){},function(){})
    config = arguments[1] || {};                                        //則將其轉換為對象
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);                          //合併預設值
  config.method = config.method ? config.method.toLowerCase() : 'get';  //ajax方法,例如:get,這裡是轉換為小寫

  // Hook up interceptors middleware
  var chain = [dispatchRequest, undefined];                             //這個是發送ajax的非同步對列
  var promise = Promise.resolve(config);                                //將config轉換為Promise對象

  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {      //請求攔截器的邏輯(下一節介紹)
    chain.unshift(interceptor.fulfilled, interceptor.rejected);                               
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {       //響應攔截的邏輯(下一節介紹)
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  while (chain.length) {                                                //如果chain.length存在
    promise = promise.then(chain.shift(), chain.shift());                 //則執行promise.then(),這裡執行dispatchRequest函數,這樣就組成了非同步隊列
  }

  return promise;                                                     //最後返回promise對象
};

這裡有一個while(chain.length){}遍歷迴圈比較難以理解,這個設計思想很新穎,這裡理解了整個axios的執行流程就能理解了,攔截器也是在這裡實現的。它就是遍歷chain數組,依次把前兩個元素分別作為promise().then的參數1和參數2來執行,這樣當promise之前的隊列執行完後就會接著執行後面的隊列,預設就是[dispatchRequest,undefined],也就是首先會執行dispatchRequest,如果有添加了請求攔截器則會在dispatchRequest之前執行攔截器里的邏輯,同樣的,如果有響應攔截器,則會在執行dispatchRequest之後執行響應攔截器里的邏輯。

dispatchRequest邏輯如下:

module.exports = function dispatchRequest(config) {                //派發一個到伺服器的請求,用config里的配置
  throwIfCancellationRequested(config);

  // Support baseURL config
  if (config.baseURL && !isAbsoluteURL(config.url)) {                //如果config.baseURL存在,且config.url不是絕對URL(以http://開頭的)
    config.url = combineURLs(config.baseURL, config.url);                //則調用combineURLs將config.baseURL拼湊在config.url的前面,我們在項目里設置的baseURL="api/"就是在這裡處理的
  }

  // Ensure headers exist
  config.headers = config.headers || {};                            //確保headers存在

  // Transform request data
  config.data = transformData(                                        //修改請求數據,會調用預設配置里的transformRequest進行處理
    config.data,
    config.headers,
    config.transformRequest
  );

  // Flatten headers
  config.headers = utils.merge(                                     //將請求頭合併為一個數組
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  utils.forEach(                                                     //再刪除config.headers里的delete、get、head、post、put、patch、common請求頭
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );
  //執行到這裡請求頭已經設置好了
  var adapter = config.adapter || defaults.adapter;                 //獲取預設配置里的adapter,也就是封裝好的ajax請求器

  return adapter(config).then(function onAdapterResolution(response) {    //執行adapter()就會發送ajax請求了,then()的第一個參數會修正返回的值
    throwIfCancellationRequested(config);

    // Transform response data
    response.data = transformData(                                         //調用預設配置里的transformResponse對返回的數據進行處理
      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);
  });
};

 最後會執行預設配置里的adapter屬性對應的函數,我們來看一下,如下:

function getDefaultAdapter() {                //獲取預設的適配器,就是Ajax的發送器吧
  var adapter;
  // Only Node.JS has a process variable that is of [[Class]] process
  if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {        //對於瀏覽器來說,用XHR adapter
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  } else if (typeof XMLHttpRequest !== 'undefined') {                                                            //對於node環境來說,則使用HTTP adapter
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  }
  return adapter;
}

var defaults = {
  adapter: getDefaultAdapter(),            //適配器
  /**/
}

./adapters/http就是最終發送ajax請求的實現,主要的邏輯如下:

module.exports = function xhrAdapter(config) {                            //發送XMLHTtpRequest()請求等
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    if (utils.isFormData(requestData)) {
      delete requestHeaders['Content-Type']; // Let the browser set it
    }

    var request = new XMLHttpRequest();

    // HTTP basic authentication
    if (config.auth) {
      var username = config.auth.username || '';
      var password = config.auth.password || '';
      requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password);
    }

    request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);        //初始化HTTP請求,採用非同步請求  調用buildURL獲取URL地址

    // Set the request timeout in MS
    request.timeout = config.timeout;                                                    //設置超時時間

    // Listen for ready state
    request.onreadystatechange = function handleLoad() {                                //綁定onreadystatechange事件
      if (!request || request.readyState !== 4) {                                            //如果HTTP響應已經還沒有接收完成
        return;                                                                                    //則直接返回,不做處理
      }

      // The request errored out and we didn't get a response, this will be
      // handled by onerror instead
      // With one exception: request that using file: protocol, most browsers
      // will return status as 0 even though it's a successful request
      if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {    //請求出錯,沒有得到響應的邏輯 如果request.responseURL不是以file:開頭且request.status=0,則直接返回
        return;
      }

      // Prepare the response
      var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null;        //解析響應頭,並調用parseHeaders將其轉換為對象,保存到responseHeaders裡面
      var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response;    //如果未設置config.responseType或者設置了responseType.responseType且等於text,則直接獲取request.responseText,否則獲取request.response
      var response = {                                            //拼湊返回的數據,也就是上一篇說的axios請求後返回的promise對象
        data: responseData,                                            //接收到的數據
        status: request.status,                                        //狀態 ie瀏覽器是用1223埠代替204埠 ,見:https://github.com/axios/axios/issues/201
        statusText: request.statusText,                                //響應頭的狀態文字
        headers: responseHeaders,                                    //頭部信息
        config: config,                                                //配置信息
        request: request                                             //對應的XmlHttpRequest對象
      };

      settle(resolve, reject, response);                        //調用settle函數進行判斷,是resolve或者reject

      // Clean up request
      request = null;
    };

    /*略,主要是對於錯誤、超時、的一些處理*/

    // Add headers to the request
    if ('setRequestHeader' in request) {                        //如果request裡面存在setRequestHeader
      utils.forEach(requestHeaders, function setRequestHeader(val, key) {                //遍歷requestHeaders
        if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') {    //如果key等於content-type 且沒有發送數據
          // Remove Content-Type if data is undefined 
          delete requestHeaders[key];                                                             //則刪除content-type這個請求頭      ;只有發送數據時content-type才有用的吧
        } else {
          // Otherwise add header to the request
          request.setRequestHeader(key, val);                                                 //否則設置請求頭
        }
      });
    }

    // Add withCredentials to request if needed
    if (config.withCredentials) {                                 //如果設置了跨域請求時使用憑證
      request.withCredentials = true;                                 //設置request.withCredentials為true
    }

    // Add responseType to request if needed
    if (config.responseType) {                                     //如果設置了伺服器響應的數據類型,預設為json
      try {
        request.responseType = config.responseType;
      } catch (e) {
        // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2.
        // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function.
        if (config.responseType !== 'json') {
          throw e;
        }
      }
    }

    // Handle progress if needed
    if (typeof config.onDownloadProgress === 'function') {                     //如果設置了下載處理進度事件
      request.addEventListener('progress', config.onDownloadProgress);
    }

    // Not all browsers support upload events
    if (typeof config.onUploadProgress === 'function' && request.upload) {     //如果設置了上傳處理進度事件
      request.upload.addEventListener('progress', config.onUploadProgress);
    }

    if (config.cancelToken) {
      // Handle cancellation
      config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
          return;
        }

        request.abort();
        reject(cancel);
        // Clean up request
        request = null;
      });
    }

    if (requestData === undefined) {                                     //修正requestData,如果為undefined,則修正為null
      requestData = null;
    }

    // Send the request
    request.send(requestData);                                             //發送數據
  });
};

也就是原生的ajax請求了,主要的邏輯都備註了一下,這樣整個流程就跑完了

對於便捷方法來說,例如axios.get()、axios.post()來說,就是對Axios.prototype.request的一次封裝,實現代碼如下:

utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {    //定義delete、get、head、options方法
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {                                             //調用utils.merge將參數合併為一個對象,然後調用request()方法
      method: method,
      url: url
    }));
  };
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {              //定義post、put、patch方法
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {                                       //調用utils.merge將參數合併為一個對象,然後調用request()方法
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data                                                                                //post、put和patch比get等請求多了個data,其它一樣的
    }));
  };
});

OK,搞定。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前面分析了memblock演算法、內核頁表的建立、記憶體管理框架的構建,這些都是x86處理的setup_arch()函數裡面初始化的,因地制宜,具有明顯處理器的特征。而start_kernel()接下來的初始化則是linux通用的記憶體管理演算法框架了。 build_all_zonelists()用來初始化 ...
  • 存儲類 存儲類(storage class)是kubernetes資源類型,它是由管理員為管理PV之便而按需創建的類別 存儲類好處是支持 PV 的動態創建,系統按PVC的需求標準動態創建適配的PV會為存儲管理帶來極大的靈活性。 PV的動態供給,其重點是在存儲類的定義,其分類大概是對存儲的性能進行分類 ...
  • 關係型資料庫-關係操作集合 1、 基本的關係操作 關係模型中常用的關係操作包括查詢(Query)操作和插入(Insert)、刪除 (Delete)、修改(Update)操作兩大部分。 查詢操作分為:選擇、投影、連接、除、並、差、交、笛卡爾積等; 五種基本操作:選擇、投影、並、差、笛卡爾積; 關係操作 ...
  • 本篇博文通過對ES中不同類型的欄位的建模方案進行說明, 並結合實際案例, 演示了index、stored、dynamic等參數的使用, 並歸納了ES處理關聯關係、避免太多的欄位、避免正則查詢、避免空值引起聚合結果失真等最佳實踐. 如有疑問, 留言區見
  • Mysql 單表查詢where初識 準備數據 數據基本測試 where 條件過濾 比較運算符 , 邏輯運算符, 範圍判斷, 空判斷, 模糊查詢 邏輯運算符: and, or, not Null 判斷 is null; is not null 範圍查詢 in; between...and in 用於離 ...
  • 關鍵詞:PostgreSQL 11、MySQL5.7 比較版本:PostgreSQL 11 VS MySQL5.7(innodb引擎) Oracle官方社區版 版權情況:PostgreSQL 11(免費開源)、MySQL5.7 Oracle官方社區版(免費開源) ...
  • MySQL作業分析 五張表的增刪改查: 完成所有表的關係創建 創建教師表(tid為這張表教師ID,tname為這張表教師的姓名) 創建班級表(cid為這張表班級ID,caption為這張表班級門號) 創建課程表(cid為這張表課程ID,cname為課程名稱,teacher_id為任課教師的ID) 創 ...
  • 今天小編要分享的還是Android Jetpack庫的基本使用方法,本篇介紹的內容是Jetpack Navigation組件,讓我們一起學習,為完成年初制定的計劃而努力吧! 組件介紹 導航,是指提供用戶在應用程式中的不同內容之間進行瀏覽、退出的交互功能。如我們在Android手機上常常用到的物理/虛 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...