前言
閱讀框架原始碼的好處在于提升編程水平,以及了解框架的設計思想,配合其官方檔案,讓我們對它的使用變得更加得心應手,
axios 是一個小而精的框架,不像 vue 那般龐大而復雜到讓人望而生畏,閱讀也起來相對容易,在閱讀原始碼前,建議掌握 call、apply、bind,Promise,閉包,this 指向,原型鏈等知識,以及一些設計模式,
axios 版本:0.18.1
1.目錄結構
axios 的原始碼在 lib 目錄,其結構如下

在正式介紹原始碼前,先回顧一下 axios 的常見的使用姿勢,以便更好的理解原始碼,
2.axios 的使用姿勢
看原始碼前必須熟練使用 axios,以下就列舉了 axios 的使用方式以及如何封裝 axios,
使用方式
方式一:axios(config)
axios({
method: "post",
url: "/user/12345",
data: {
firstName: "Fred",
lastName: "Flintstone",
},
});
方式二:axios(url[, config])
axios("/user/12345", {
/* config */
});
方式三:axios[method](url[, config])
axios.get("/user/12345", {
/* config */
});
這種方式針對 get、delete、head、options 方法
方式四:axios[method](url[, data[, config]])
axios.post(
"/user/12345",
{
firstName: "Fred",
lastName: "Flintstone",
},
{
/* config */
}
);
這種方式針對 post、put、patch 方法
方式五:axios.request(config)
axios.request({
method: "post",
url: "/user/12345",
data: {
firstName: "Fred",
lastName: "Flintstone",
},
});
是否會好奇 axios 如何實作支持這五種方式的?后續將會解答,
封裝 axios
有時我們更傾向于創建一個 Axios 實體,然后設定攔截器,
import axios from "axios";
import { BASE_URL } from "./http";
// create an axios instance
const service = axios.create({
baseURL: BASE_URL, // url = base url + request url
withCredentials: true, // send cookies when cross-domain requests(是否支持跨域)
timeout: 5000, // request timeout(超時時間)
});
// request interceptor(請求攔截器)
service.interceptors.request.use(
(config) => {
// do something before request is sent
return config;
},
(error) => {
// do something with request error
// console.log(error) // for debug
return Promise.reject(error);
}
);
// response interceptor(回應攔截器)
service.interceptors.response.use(
(response) => {
const res = response.data;
return res;
},
(error) => {
if (error.response) {
// console.log('err' + error) // for debug
switch (error.response.status) {
// 不同狀態碼下執行不同操作
case 401:
break;
case 404:
break;
case 500:
break;
default:
}
}
return Promise.reject(error);
}
);
export default service;
攔截器是 axios 最核心的一個功能,通過閱讀原始碼將了解其攔截器是如何實作的,
3.從 axios.js 開始
axios.js 中主要包含以下部分:
- 第一部分:bind、utils 工具方法;
- 第二部分:axios、axios.Axios、axios.create;
- 第三部分:取消相關的方法(Cancel、CancelToken、isCancel)、all、spread 處理并發請求的助手函式(這里暫不介紹),
這里著重介紹第二部分的實作,
axios.js 原始碼如下:
"use strict";
var utils = require("./utils");
var bind = require("./helpers/bind");
var Axios = require("./core/Axios");
var defaults = require("./defaults");
/**
* 創建一個 Axios 實體
*
* @param {Object} defaultConfig 實體的配置
* @return {Axios} 一個 Axios 實體
*/
function createInstance(defaultConfig) {
// 創建一個 Axios 實體
var context = new Axios(defaultConfig);
// bind 回傳一個函式實體(這里是 wrap 方法)
// 修改 Axios.prototype.request 的 this 指向為 context
// 執行 axios(config) 相當于執行 Axios.prototype.request(config)
var instance = bind(Axios.prototype.request, context);
// 把 Axios.prototype 的所有屬性/方法都復制到 instance 中, context 作為 bind 的作用域
// Axios.prototype 上有 request, get, post 等 8 個方法
utils.extend(instance, Axios.prototype, context);
// 把 context 的所有屬性都復制到 instance 中
// context 包含 defaults 默認配置 和 interceptors 攔截器
utils.extend(instance, context);
return instance;
}
// 創建要匯出的默認實體
var axios = createInstance(defaults);
// 暴露 Axios 類以允許類繼承
axios.Axios = Axios;
// 暴露創建 Axios 實體的工廠方法 【工廠模式】
axios.create = function create(instanceConfig) {
// merge: 將用戶傳入的配置與默認配置進行合并
return createInstance(utils.merge(defaults, instanceConfig));
};
// 暴露取消 request 相關的方法
axios.Cancel = require("./cancel/Cancel");
axios.CancelToken = require("./cancel/CancelToken");
axios.isCancel = require("./cancel/isCancel");
// 暴露 all/spread (處理并發請求的助手函式)
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require("./helpers/spread");
module.exports = axios;
// 允許在 TypeScript 中使用默認匯入語法
module.exports.default = axios;
注:代碼中的 bind、utils 后面有介紹,
從這個檔案中可以看到其對外暴露了一個 Axios 實體 axios,axios 是通過 createInstance() 方法創建的,
這里不妨列印一下 axios(直接在 VScode 中執行當前檔案),其結果如下:
function wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
}
可以看到,axios 其實就是一個 wrap() 方法,結合上面的原始碼知道,fn 為 Axios.prototype.request,thisArg 為 context(Axios 實體),所以執行 axios(config) 就相當于執行 Axios.prototype.request(config),
如果好奇 context 長什么模樣也可列印一下,其結果如下:
Axios {
defaults: {
adapter: [Function: httpAdapter],
transformRequest: [ [Function: transformRequest] ],
transformResponse: [ [Function: transformResponse] ],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1,
validateStatus: [Function: validateStatus],
headers: {
common: [Object],
delete: {},
get: {},
head: {},
post: [Object],
put: [Object],
patch: [Object]
}
},
interceptors: {
request: InterceptorManager { handlers: [] },
response: InterceptorManager { handlers: [] }
}
}
注:這里的方括號‘[]’并不一定是陣列,比如 adapter: [Function: httpAdapter],
可以看到,列印的是一個 Axios 類的實體物件,包含 defaults(默認配置)和 interceptors(攔截器),至于具體細節放在后面再介紹,
再看 utils.extend(instance, Axios.prototype, context); 代碼,
這步操作是在將 Axios.prototype 的所有屬性/方法都復制到 instance 中, context 作為 bind 時的作用域(見 utils.entend 內部實作),這里的 instance 其實就是 axios;context 上面已經列印了,是 Axios 的默認實體,Axios.prototype 又包含啥呢?列印結果如下:
Axios {
request: [Function: request],
delete: [Function],
get: [Function],
head: [Function],
options: [Function],
post: [Function],
put: [Function],
patch: [Function]
}
所以經過 utils.extend 后 instance 就并不是一個簡單的 wrap 方法,其上還包括了 Axios.prototype 中的 8 個方法,因此,這就解釋了為什么可以通過 axios.get 等方式(文章開頭時 axios 的使用方式 三、四、五)進行發送請求,
最后 utils.extend(instance, context);,
這里將 context 中的 defaults、interceptors(攔截器)擴展到了 instance 中,所以,在回頭看看在 封裝 axios 中為什么能通過 service.interceptors 設定攔截器就不感到奇怪了吧,
看到這兒,你如果感覺有些暈,說明你還對 axios 整體結構還不熟悉,不妨繼續往下,之后再回頭看一遍,
4.第一部分:bind、utils 工具方法
bind 方法
"use strict";
// 手寫實作一個 bind 方法,兼容低版本瀏覽器
module.exports = function bind(fn, thisArg) {
return function wrap() {
// 將 arguments 轉為陣列
// 注: 這里是否多余?apply 支持 arguments 類陣列物件
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
// 執行 fn 并傳入作用域和引數
return fn.apply(thisArg, args);
};
};
utils 中的方法有很多,這里只介紹下面三個,
utils.forEach
/**
* forEach 方法的實作,并支持物件的遍歷,【迭代器模式】
* 遍歷陣列和物件,為每一項呼叫一個函式 fn
*
* @param {Object|Array} obj 迭代的物件
* @param {Function} fn 為每一項執行的回呼函式
*/
function forEach(obj, fn) {
// 不是一個有效值時,退出
if (obj === null || typeof obj === "undefined") {
return;
}
// 不是一個可迭代的值時
if (typeof obj !== "object") {
obj = [obj];
}
if (isArray(obj)) {
// 遍歷并覆寫陣列值
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// 遍歷并覆寫物件值
// for in 會遍歷原型上的屬性, 這里用 hasOwnProperty() 過濾掉原型上的屬性
for (var key in obj) {
// 判斷鍵值是否存在
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
utils.merge
/**
* 物件屬性進行合并,后面物件屬性優先級高于前面物件屬性
*
* 例如:
* ```js
* var result = merge({foo: 123}, {foo: 456});
* console.log(result.foo); // 輸出 456
* ```
*
* @param {Object} obj1 合并物件
* @returns {Object} 所有合并屬性的結果
*/
function merge(/* obj1, obj2, obj3, ... */) {
var result = {};
function assignValue(val, key) {
if (typeof result[key] === "object" && typeof val === "object") {
// 若是物件型別的值,則繼續遞回
result[key] = merge(result[key], val);
} else {
result[key] = val;
}
}
// 遍歷引數
for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
return result;
}
utils.extend
/**
* 將 b 的屬性復制到 a 上,屬性名相同則覆寫
*
* @param {Object} a 要擴展的物件
* @param {Object} b 要復制屬性的物件
* @param {Object} thisArg 要系結功能的物件
* @return {Object} 物件 a 的結果值
*/
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
if (thisArg && typeof val === "function") {
// 是函式
a[key] = bind(val, thisArg);
} else {
// 不是函式
a[key] = val;
}
});
return a;
}
5.第二部分:axios、axios.Axios、axios.create
axios、axios.create 上面已經介紹了,下面主要介紹 Axios 類的實作,
Axios.js
這個檔案宣告了一個 Axios 構造器,然后在其原型上添加了 request、delete、get、head、options、post、put、patch 8 個方法,
"use strict";
var defaults = require("./../defaults");
var utils = require("./../utils");
var InterceptorManager = require("./InterceptorManager");
var dispatchRequest = require("./dispatchRequest");
/**
* Axios 構造器
*
* @param {Object} instanceConfig 實體的配置引數
*/
function Axios(instanceConfig) {
// 設定請求配置
this.defaults = instanceConfig;
// 為請求和回應添加 interceptors (攔截器)
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
/**
* 發送請求
*
* @param {Object} config 用戶傳入的配置 (將其合并到默認配置 defaults 上)
*/
Axios.prototype.request = function request(config) {
// 如果 config 是一個字串,則將其當作 url
if (typeof config === "string") {
config = utils.merge(
{
url: arguments[0],
},
arguments[1]
);
}
// 將配置進行合并
// defaults: Axios 默認配置
// {method: 'get'}: 默認是 get 方法
// this.defaults: Axios 構造器中的配置屬性
// config: 用戶傳入的配置
config = utils.merge(defaults, { method: "get" }, this.defaults, config);
config.method = config.method.toLowerCase(); // 方法名轉小寫
// 連接攔截器中間件
// undefined: 后面需要兩兩取出, 用于占位
var chain = [dispatchRequest, undefined];
// 將 config 轉換為 promise, 同時后續 config 將作為引數進行傳遞
var promise = Promise.resolve(config);
// 將 request 攔截器逐一插入到 鏈表的頭部
// 注:這里的 forEach 是 InterceptorManager.prototype.forEach
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);
});
// 通過上面的步驟, 我們就形成了如下鏈表:
// request 攔截器(interceptors.request) + 發起請求的方法(dispatchRequest) + response 攔截器(interceptors.response)
while (chain.length) {
// 從鏈表中從頭連續取出2個元素,第一個作為 promise 的 resolve handler, 第二個作為 reject handler
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
// 提供支持的請求方法的別名
// 注意這四個方法都不需要 請求負載(request payload)
utils.forEach(
["delete", "get", "head", "options"],
function forEachMethodNoData(method) {
Axios.prototype[method] = function (url, config) {
// this.request: 呼叫 Axios.prototype.request
// 將引數合并為一個, config 非必填
return this.request(
utils.merge(config || {}, {
method: method,
url: url,
})
);
};
}
);
// 注意這四個方法都有 請求負載(request payload)
utils.forEach(["post", "put", "patch"], function forEachMethodWithData(method) {
Axios.prototype[method] = function (url, data, config) {
// this.request: 呼叫 Axios.prototype.request
// 將引數合并為一個, config 非必填
return this.request(
utils.merge(config || {}, {
method: method,
url: url,
data: data,
})
);
};
});
module.exports = Axios;
注:請求負載(request payload),就是傳遞的 data 資料,
通過上面的代碼可以發現,不管什么方法,其最后都是執行 request 方法,
request 方法接收一個 config 引數,若 config 是字串,則將 arguments[0] 當作 url,arguments[1] 當作其他配置引數,所以,這就是為什么即能夠通過 axios(config) 傳參方式也能夠通過 axios(url[, config]) 的傳參方式進行訪問的原因,當然,還有一種傳參方式 axios[method](url[, data[, config]])(只支持 post、put、patch),通過其 post、put、patch 的實作就能看明白原因,
接著往下,開始合并 config,然后將 config.method 轉換為小寫,
然后,最重要的部分到了,定義一個鏈表 chain,然后通過 Promise.resolve() 將 config 轉換為 promise,再將請求與回應攔截器添加到鏈表頭尾,最終,回傳的是一個 promise,
由于這里涉及攔截器,所以先看看 InterceptorManager.js,
InterceptorManager.js
InterceptorManager.js 中宣告了一個 InterceptorManager 攔截器管理類,然后在其原型上添加了 use、eject、forEach 方法,
"use strict";
var utils = require("./../utils");
/**
* 宣告一個攔截器管理的類
*/
function InterceptorManager() {
// 用一個陣列存放所有攔截器
this.handlers = [];
}
/**
* 添加一個攔截器
*
* @param {Function} fulfilled 成功的回呼
* @param {Function} rejected 失敗的回呼
* @return {Number} 回傳添加的攔截器的下標, 用于以后洗掉
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
});
return this.handlers.length - 1;
};
/**
* 洗掉一個攔截器
*
* @param {Number} id 通過 use 方法回傳的 id
*/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
/**
* 遍歷所有非 null 的 handler, 并呼叫
*
* @param {Function} fn 呼叫每個攔截器的函式
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
// 這里的 h 就是 handlers 中的值
if (h !== null) {
fn(h);
}
});
};
module.exports = InterceptorManager;
結合最開始的 封裝 axios 部分代碼,簡寫如下示例代碼:
// -示例代碼-
// 創建一個 axios 實體
const service = axios.create({
baseURL: "baseurl",
withCredentials: true,
timeout: 5000,
});
// 添加請求攔截器
service.interceptors.request.use(
(config) => config, // fn1
(error) => Promise.reject(error) // fn2
);
// 添加回應攔截器
service.interceptors.response.use(
(response) => response.data, // fn3
(error) => Promise.reject(error) // fn4
);
// 發送請求
service({
url: "/api",
method: "POST",
data: {
name: "Bob",
},
})
.then((res) => {
// 拿到結果
})
.catch((err) => {
// 錯誤資訊
});
結合上面的示例代碼、InterceptorManager.js 、Axios.js 就更容易理解攔截器的實作與運行流程,
這里執行 service(config) 就是在執行 Axios.prototype.request(config),最終 chain 鏈表如下:
[
[Function], // fn1
[Function], // fn2
[(Function: dispatchRequest)],
undefined,
[Function], // fn3
[Function], // fn4
];
其執行程序為:fn1,fn2(request 攔截器) => dispatchRequest(發起請求的方法) => fn3,fn4(response 攔截器),
接下來就開始查看 dispatchRequest 的實作了,
dispatchRequest.js
"use strict";
var utils = require("./../utils");
var transformData = require("./transformData");
var isCancel = require("../cancel/isCancel");
var defaults = require("../defaults");
var isAbsoluteURL = require("./../helpers/isAbsoluteURL");
var combineURLs = require("./../helpers/combineURLs");
/**
* 如果請求已取消,則拋出“取消”,
*/
function throwIfCancellationRequested(config) {
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
}
/**
* 使用配置的配接器向服務器發送請求,
*
* @param {object} config 用于請求的配置
* @returns {Promise} 回傳 fulfilled(已成功) 狀態的 Promise
*/
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// 如果配置了 baseURL, 且 config.url 也不是絕對路徑, 則拼接
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}
// 確保 headers 存在
config.headers = config.headers || {};
// 對即將發起的請求的 資料 和 header 做預處理
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
// 合并不同配置的 header
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);
// 洗掉 headers 中的 method
utils.forEach(
["delete", "get", "head", "post", "put", "patch", "common"],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
// 配接器
var adapter = config.adapter || defaults.adapter;
// 使用配接器發起請求
return adapter(config).then(
function onAdapterResolution(response) {
// 請求成功
throwIfCancellationRequested(config);
// 轉換回應資料
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
// 回傳回應資料
return response;
},
function onAdapterRejection(reason) {
// 請求失敗
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// 如果請求錯誤且有資料
// 轉換回應資料
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
// 回傳錯誤資訊,通過 catch 捕獲
return Promise.reject(reason);
}
);
};
dispatchRequest(config) 回傳的是 adapter(config) 執行的結果
接著需要看 adapter 的實作,這里的 adapter 一般是從 defaults 中獲取的,所以先看看 defaults,
defaults.js
"use strict";
var utils = require("./utils");
var normalizeHeaderName = require("./helpers/normalizeHeaderName");
// 默認 Content-Type
var DEFAULT_CONTENT_TYPE = {
"Content-Type": "application/x-www-form-urlencoded",
};
// 如果 headers 中 Content-Type 不存在,則設定其值
function setContentTypeIfUnset(headers, value) {
if (
!utils.isUndefined(headers) &&
utils.isUndefined(headers["Content-Type"])
) {
headers["Content-Type"] = value;
}
}
// 不同環境下,獲取默認的配接器
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== "undefined") {
// 瀏覽器環境使用 XHR
adapter = require("./adapters/xhr");
} else if (typeof process !== "undefined") {
// node 環境使用 HTTP
adapter = require("./adapters/http");
}
return adapter;
}
var defaults = {
// 默認請求方式
adapter: getDefaultAdapter(),
// 格式化 requestData
transformRequest: [
function transformRequest(data, headers) {
// 將 headers 中不標準的屬性名,格式化為 Content-Type 屬性名
normalizeHeaderName(headers, "Content-Type");
// 如果是以下幾種格式, 則直接回傳
if (
utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
// 如果是 URLSearchParams 物件, 設定 Content-Type
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(
headers,
"application/x-www-form-urlencoded;charset=utf-8"
);
return data.toString();
}
// 如果是 物件, 設定 Content-Type
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, "application/json;charset=utf-8");
return JSON.stringify(data);
}
return data;
},
],
// 格式化 responseData, 將其決議為物件
transformResponse: [
function transformResponse(data) {
if (typeof data === "string") {
try {
data = JSON.parse(data);
} catch (e) {
/* Ignore */
} // 這里并未對 JSON.parse() 的錯誤做處理
}
return data;
},
],
// 設定請求超時時間, 默認為 0. 超時將終止請求, 0 意味著沒有超時
timeout: 0,
// CSRF/XSRF (跨站請求偽造)
// `xsrfCookieName` 是要用作 xsrf 令牌的值的 cookie 的名稱
// `xsrfHeaderName` 是攜帶 xsrf 令牌值的 http 頭的名稱
xsrfCookieName: "XSRF-TOKEN",
xsrfHeaderName: "X-XSRF-TOKEN",
// 定義允許的回應內容的最大尺寸
maxContentLength: -1,
// 驗證請求狀態
// `validateStatus` 定義對于給定的 HTTP 回應狀態碼是 resolve 或 reject 的 promise ,
// 如果 `validateStatus` 回傳 `true` (或者設定為 `null` 或 `undefined`),
// promise 將被 resolve; 否則, promise 將被 reject
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
},
};
defaults.headers = {
// 通用的 HTTP 欄位
common: {
Accept: "application/json, text/plain, */*",
},
};
// 添加 請求方法
utils.forEach(["delete", "get", "head"], function forEachMethodNoData(method) {
defaults.headers[method] = {}; // 沒有 Content-Type
});
// 設定默認的 Content-Type
utils.forEach(["post", "put", "patch"], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});
module.exports = defaults;
defaults 中包含的內容整理如下圖:

adapter 這里只介紹 xhr,
xhr.js
"use strict";
var utils = require("./../utils");
var settle = require("./../core/settle");
var buildURL = require("./../helpers/buildURL");
var parseHeaders = require("./../helpers/parseHeaders");
var isURLSameOrigin = require("./../helpers/isURLSameOrigin");
var createError = require("../core/createError");
// 瀏覽器環境下使用的方法
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
var requestData = config.data;
var requestHeaders = config.headers;
// 如果是 FormData 物件, 洗掉 headers 中的 Content-Type 欄位, 讓瀏覽器自動生成
if (utils.isFormData(requestData)) {
delete requestHeaders["Content-Type"];
}
// 創建一個 xhr 物件
// 參考: https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest
var request = new XMLHttpRequest();
// 配置 HTTP 請求頭的 Authentication [HTTP身份驗證]
// 參考: https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Authentication
if (config.auth) {
var username = config.auth.username || "";
var password = config.auth.password || "";
// 使用 btoa 創建一個 base-64 編碼的字串
requestHeaders.Authorization = "Basic " + btoa(username + ":" + password);
}
// open(請求方法, 請求URL, 是否支持異步): 初始化一個請求
request.open(
config.method.toUpperCase(),
buildURL(config.url, config.params, config.paramsSerializer),
true
);
// 請求的最大請求時間, 超出則終止請求
request.timeout = config.timeout;
// 監聽 readyState 屬性變化, 其值為 4 時表示請求成功
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// 在請求完成前, status 的值為 0,如果 XMLHttpRequest 出錯, 瀏覽器回傳的 status 也為 0,
// file 協議除外, status 為 0 時也是一個成功的請求
// responseURL: 回傳經過序列化(serialized)的回應 URL
if (
request.status === 0 &&
!(request.responseURL && request.responseURL.indexOf("file:") === 0)
) {
return;
}
// 準備回應
// getAllResponseHeaders 回傳所有回應頭
// responseText 回傳的文本資料
var responseHeaders =
"getAllResponseHeaders" in request
? parseHeaders(request.getAllResponseHeaders())
: null;
var responseData =
!config.responseType || config.responseType === "text"
? request.responseText
: request.response;
var response = {
data: responseData, // 回應正文
status: request.status, // 回應狀態
statusText: request.statusText, // 回應狀態的文本資訊
headers: responseHeaders, // 回應頭
config: config,
request: request,
};
// status >= 200 && status < 300 resolve
// 否則 reject
settle(resolve, reject, response);
// 清除 request
request = null;
};
// 請求出錯
request.onerror = function handleError() {
// 拋出網路錯誤
reject(createError("Network Error", config, null, request));
// 清除 request
request = null;
};
// 請求超時
request.ontimeout = function handleTimeout() {
// 拋出超時錯誤
reject(
createError(
"timeout of " + config.timeout + "ms exceeded",
config,
"ECONNABORTED",
request
)
);
// 清除 request
request = null;
};
// 增加 xsrf header
// 僅在標準瀏覽器環境中運行時才能執行此操作,
// xsrf header 是用來防御 CSRF 攻擊
if (utils.isStandardBrowserEnv()) {
var cookies = require("./../helpers/cookies");
// 增加 xsrf header
var xsrfValue =
(config.withCredentials || isURLSameOrigin(config.url)) &&
config.xsrfCookieName
? cookies.read(config.xsrfCookieName)
: undefined;
if (xsrfValue) {
requestHeaders[config.xsrfHeaderName] = xsrfValue;
}
}
// 將 config 中配置的 requestHeaders, 回圈設定到請求頭上
if ("setRequestHeader" in request) {
utils.forEach(requestHeaders, function setRequestHeader(val, key) {
if (
typeof requestData === "undefined" &&
key.toLowerCase() === "content-type"
) {
// 如果 data 未定義, 移除 Content-Type
delete requestHeaders[key];
} else {
// 設定 request header
request.setRequestHeader(key, val);
}
});
}
// `withCredentials` 表示跨域請求時是否需要使用憑證
if (config.withCredentials) {
request.withCredentials = true;
}
// `responseType` 表示服務器回應的資料型別,
// 可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
if (config.responseType) {
try {
request.responseType = config.responseType;
} catch (e) {
// DOMException 拋出瀏覽器不兼容 XMLHttpRequest Level 2.
// 但是,對于 “json” 型別,可不拋出錯誤,因為可以通過默認的 transformResponse 方法對其進行決議,
if (config.responseType !== "json") {
throw e;
}
}
}
// `onDownloadProgress` 允許為下載處理進度事件
if (typeof config.onDownloadProgress === "function") {
request.addEventListener("progress", config.onDownloadProgress);
}
// `onUploadProgress` 允許為上傳處理進度事件
if (typeof config.onUploadProgress === "function" && request.upload) {
request.upload.addEventListener("progress", config.onUploadProgress);
}
if (config.cancelToken) {
// 取消操作
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// 清除 request
request = null;
});
}
if (requestData === undefined) {
requestData = null;
}
// 發送請求
request.send(requestData);
});
};
xhrAdapter 回傳一個 Promise,執行 adapter(config) 就相當于執行 dispatchXhrRequest,因此,可以得到如下執行流程:

結尾
到這兒其實我們就對 axios 核心原始碼部分有了解了,其中涉及多種涉及模式,比如工廠模式、迭代器模式、配接器模式、中間件等,其中較為精華的部分便是其使用中間件實作攔截器,這部分內容不妨多看兩遍,
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/266398.html
標籤:其他
下一篇:Shell 一鍵安裝命令
