前言
阅读框架源码的好处在于提升编程水平,以及了解框架的设计思想,配合其官方文档,让我们对它的使用变得更加得心应手。
axios 是一个小而精的框架,不像 vue 那般庞大而复杂到让人望而生畏,阅读也起来相对容易。在阅读源码前,建议掌握 call、apply、bind,Promise,闭包,this 指向,原型链等知识,以及一些设计模式。
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 核心源码部分有了解了。其中涉及多种涉及模式,比如工厂模式、迭代器模式、适配器模式、中间件等。其中较为精华的部分便是其使用中间件实现拦截器,这部分内容不妨多看两遍。