Fork me on GitHub

axios源码阅读笔记

仓库地址:https://github.com/axios/axios

Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。

axios功能

  • 在浏览器环境创建 XMLHttpRequests
  • 在 node.js 创建 http 请求
  • 支持promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

通过源码来理清这些功能的实现原理

axios使用

中文说明文档: https://www.kancloud.cn/yunye/axios/234845

执行get请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

// 可选地,上面的请求可以这样做
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

指向post请求:

1
2
3
4
5
6
7
8
9
10
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

源码阅读笔记

1.axios/lib/axios.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
'use strict';

var utils = require('./utils');
var bind = require('./helpers/bind');
var Axios = require('./core/Axios');
var defaults = require('./defaults');

/**
* Create an instance of Axios
*/
function createInstance(defaultConfig) {
// 实例化 Axios
var context = new Axios(defaultConfig);
// 自定义bind方法,返回一个函数()=> {Axios.prototype.request.apply(context,args)}
var instance = bind(Axios.prototype.request, context);

// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context);

// Copy context to instance
utils.extend(instance, context);

return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;

// Factory for creating new instances
axios.create = function create(instanceConfig) {
return createInstance(utils.merge(defaults, instanceConfig));
};

// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');

// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

**2.axios/lib/util.js方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
module.exports = {
// Object.prototype.toString.call(val) === '[object Array]'
isArray: isArray,
// Object.prototype.toString.call(val) === '[object ArrayBuffer]'
isArrayBuffer: isArrayBuffer,
isBuffer: isBuffer,
isFormData: isFormData,
isArrayBufferView: isArrayBufferView,
// typeof val === 'string'
isString: isString,
// typeof val === 'number'
isNumber: isNumber,
// val !== null && typeof val === 'object'
isObject: isObject,
// typeof val === 'undefined'
isUndefined: isUndefined,
// Object.prototype.toString.call(val) === '[object Date]'
isDate: isDate,
// Object.prototype.toString.call(val) === '[object File]'
isFile: isFile,
// Object.prototype.toString.call(val) === '[object Blob]'
isBlob: isBlob,
// Object.prototype.toString.call(val) === '[object Function]'
isFunction: isFunction,
// isObject(val) && isFunction(val.pipe)
isStream: isStream,
// typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
isURLSearchParams: isURLSearchParams,
//
isStandardBrowserEnv: isStandardBrowserEnv,
forEach: forEach,
merge: merge,
extend: extend,
// str.replace(/^\s*/, '').replace(/\s*$/, '');
trim: trim
};

is开头的isXxx方法名 都是判断是否是 Xxx 类型

extend 将 b 里面的属性和方法继承给 a , 并且将 b 里面的方法的执行上个下文都绑定到 thisArg

1
2
3
4
5
6
7
8
9
10
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;
}

extent中有个bind方法,在axios/lib/helpers/bind.js

1
2
3
4
5
6
7
8
9
module.exports = function bind(fn, thisArg) {
return 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);
};
};

自定义 forEach 方法支持遍历基本数据,数组,对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function forEach(obj, fn) {
// Don't bother if no value provided
if (obj === null || typeof obj === 'undefined') {
return;
}

// Force an array if not already something iterable
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj];
}

if (isArray(obj)) {
// Iterate over array values
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// Iterate over object keys
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}

merge 合并对象的属性,相同属性后面的替换前的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}

回看上面的重点方法 createInstance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createInstance(defaultConfig) {
// 实例化 Axios
var context = new Axios(defaultConfig);

// 将 Axios.prototype.request 的执行上下文绑定到 context
// bind 方法返回的是一个函数
var instance = bind(Axios.prototype.request, context);

// 将 Axios.prototype 上的所有方法的执行上下文绑定到 context , 并且继承给 instance
utils.extend(instance, Axios.prototype, context);

// 将 context 继承给 instance
utils.extend(instance, context);

return instance;
}
// 传入一个默认配置
var axios = createInstance(defaults);

createInstance 函数返回了一个函数 instance.

  • instance 是一个函数 Axios.prototype.request 且执行上下文绑定到 context。
  • instance 里面还有 Axios.prototype 上面的所有方法,并且这些方法的执行上下文也绑定到 context。
  • instance 里面还有 context 上的方法。

2.axios/lib/core/Axios.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
'use strict';

var defaults = require('./../defaults');
var utils = require('./../utils');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');

/**
* Create a new instance of Axios
*/
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}

/**
* Dispatch a request
*/
Axios.prototype.request = function request(config) {
// 核心方法 request
};

// 给 Axios.prototype 上面增加 delete,get,head,options 方法
// 这样就可以使用 axios.get(), axios.post() 等等方法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});

module.exports = Axios;

上面的所有的方法都是通过调用了 this.request 方法,使用到了 Promise 的链式调用,也使用到了中间件的思想。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}

Axios.prototype.request = function request(config) {
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = utils.merge({
url: arguments[0]
}, arguments[1]);
}

// 合并配置,请求方式,没有默认为 get
config = utils.merge(defaults, {method: 'get'}, this.defaults, config);
config.method = config.method.toLowerCase();

// 重点 这个就是拦截器的中间件
var chain = [dispatchRequest, undefined];
// 生成一个 promise 对象
var promise = Promise.resolve(config);

// 将请求前方法置入 chain 数组的前面 一次置入两个 成功的,失败的,注意forEach是在InterceptorManager.prototype上添加的
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});

// // 将请求后的方法置入 chain 数组的后面 一次置入两个 成功的,失败的
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});

// 通过 shift 方法把第一个元素从其中删除,并返回第一个元素。
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}

return promise;
};

拦截器的作用就是在请求或响应被 then 或 catch 处理前拦截它们,大致用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});

通过 promise 链式调用一个一个函数,这个函数就是 chain 数组里面的方法

1
2
3
4
5
6
7
8
9
10
11
12
// 初始的 chain 数组 dispatchRequest 是发送请求的方法
var chain = [dispatchRequest, undefined];

// 然后 遍历 interceptors
// 现在只要理解他是遍历给 chain 里面追加两个方法就可以
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);
});
  • chain是一个执行队列。这个队列的初始值,是一个带有config参数的Promise
  • 在chain执行队列中,插入了初始的发送请求的函数dispatchReqeust和与之对应的undefined。后面需要增加一个undefined是因为在Promise中,需要一个success和一个fail的回调函数,这个从代码promise = promise.then(chain.shift(), chain.shift());就能够看出来。因此,dispatchReqeust和undefined我们可以成为一对函数。
  • 在chain执行队列中,发送请求的函数dispatchReqeust是处于中间的位置。它的前面是请求拦截器,通过unshift方法放入;它的后面是响应拦截器,通过push放入。要注意的是,这些函数都是成对的放入,也就是一次放入两个。

3.axios/lib/core/InterceptorManager.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
'use strict';

var utils = require('./../utils');

function InterceptorManager() {
// 存放方法的数组
this.handlers = [];
}

// 通过 use 方法来添加拦截方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};

// 通过 eject 方法来删除拦截方法
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};

// 添加一个 forEach 方法,这就是上述说的 forEach
InterceptorManager.prototype.forEach = function forEach(fn) {
// 里面还是依旧使用了 utils 的 forEach,不要纠结这些 forEach 的具体代码
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};

module.exports = InterceptorManager;

4.axios/lib/core/dispatchRequest.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
'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();
}
}

module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);

//如果请求没有取消,执行请求逻辑
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}

// Ensure headers exist
config.headers = config.headers || {};

// 转换数据
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);

// 合并配置
config.headers = utils.merge(
config.headers.common || {},
config.headers[config.method] || {},
config.headers || {}
);

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);

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

return Promise.reject(reason);
});
};

在最开始实例化 createInstance 方法中我们传入的 defaults是什么?

5.axios/lib/defaults.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
'use strict';

var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');

var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded'
};

function setContentTypeIfUnset(headers, value) {
if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) {
headers['Content-Type'] = value;
}
}

// getDefaultAdapter 方法是来获取请求的方式
function getDefaultAdapter() {
var adapter;
// 如果是浏览器就用 XMLHttpRequest
if (typeof XMLHttpRequest !== 'undefined') {
adapter = require('./adapters/xhr');
// process 是 node 环境的全局变量
} else if (typeof process !== 'undefined') {
// 如果是 node 环境那么久通过 node http 的请求方法
adapter = require('./adapters/http');
}
return adapter;
}

var defaults = {
// adapter 就是请求的方法
adapter: getDefaultAdapter(),
// 下面一些请求头,转换数据,请求,详情的数据
// 这也就是为什么我们可以直接拿到请求的数据时一个对象,如果用 ajax 我们拿到的都是 jSON 格式的字符串
// 然后每次都通过 JSON.stringify(data)来处理结果。
transformRequest: [function transformRequest(data, headers) {
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;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data)) {
setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
return JSON.stringify(data);
}
return data;
}],

transformResponse: [function transformResponse(data) {
/*eslint no-param-reassign:0*/
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}],

/**
* A timeout in milliseconds to abort a request. If set to 0 (default) a
* timeout is not created.
*/
timeout: 0,

xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',

maxContentLength: -1,

validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
}
};

defaults.headers = {
common: {
'Accept': 'application/json, text/plain, */*'
}
};

utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) {
defaults.headers[method] = {};
});

utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE);
});

module.exports = defaults;

6.axios/lib/cancel

取消http请求:在完成搜索相关的功能时,我们经常会需要频繁的发送请求来进行数据查询的情况。通常来说,我们在下一次请求发送时,就需要取消上一次请求。因此,取消请求相关的功能也是一个优点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// handle error
}
});

axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})

// cancel the request (the message parameter is optional)
source.cancel('Operation canceled by the user.');

首先是元数据Cancel类,它是用来记录取消状态一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict';

/**
* A `Cancel` is an object that is thrown when an operation is canceled.
*/
function Cancel(message) {
this.message = message;
}

Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};

Cancel.prototype.__CANCEL__ = true;

module.exports = Cancel;

在CancelToken类中,它通过传递一个Promise的方法来实现了HTTP请求取消

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
'use strict';

var Cancel = require('./Cancel');

/**
* A `CancelToken` is an object that can be used to request cancellation of an operation.
*/
function CancelToken(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}

var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});

var token = this;
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}

token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}

/**
* Throws a `Cancel` if cancellation has been requested.
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};

/**
* 返回一个CancelToken类的实例token和一个函数cancel
* cancels the `CancelToken`.
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};

module.exports = CancelToken;

在adapter/xhr.js文件中,有与之相对应的取消请求的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (config.cancelToken) {
// 等待取消
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}

request.abort();
reject(cancel);
// 重置请求
request = null;
});
}

结合代码总结实现逻辑如下:

  1. 在可能需要取消的请求中,初始化时调用了source方法,这个方法返回了一个CancelToken类的实例A和一个函数cancel。
  2. 在source方法返回实例A中,初始化了一个在pending状态的promise。将整个实例A传递给axios后,这个promise被用于做取消请求的触发器。
  3. 当source方法返回的cancel方法被调用时,实例A中的promise状态由pending变成了fulfilled,立刻触发了then的回调函数,从而触发了axios的取消逻辑——request.abort(),非常巧妙~

axios的设计值的借鉴之处

  • 发送请求函数的处理逻辑

axios在处理发送请求的dispatchRequest函数时,没有当做一个特殊的函数来对待,而是采用一视同仁的方法,将其放在队列的中间位置,从而保证了队列处理的一致性,提高了代码的可阅读性。

  • Adapter的处理逻辑

在adapter的处理逻辑中,axios没有把http和xhr两个模块(一个用于Node.js发送请求,另一个则用于浏览器端发送请求)当成自身的模块直接在dispatchRequest中直接饮用,而是通过配置的方法在default.js文件中进行默认引入。这样既保证了两个模块间的低耦合性,同时又能够为今后用户需要自定义请求发送模块保留了余地。

  • 取消HTTP请求的处理逻辑

在取消HTTP请求的逻辑中,axios巧妙的使用了一个Promise来作为触发器,将resolve函数通过callback中参数的形式传递到了外部。这样既能够保证内部逻辑的连贯性,也能够保证在需要进行取消请求时,不需要直接进行相关类的示例数据改动,最大程度上避免了侵入其他的模块。

彩蛋:如何取消上一个页面所有请求

SPA时代经常会遇到当前页面未加载完毕时跳转路由或者返回操作, 但是通过network会发现, 若网络环境较差的情况下, 会一直pending, 切换路由后在network中添加新的请求但是正在pending的请求依然存在. 当我们在项目中做了一个上拉加载分页的时候会一直加载中, 用户等待不耐烦后可能会主动触发返回操作, 但是此刻即使用户触发返回操作, 加载分页的请求还是存在, 页面还是会一直提示加载中, 直到该请求加载成功或超时才肯罢休

实现的思路是将cancel放置于全局变量的数组中, 可以挂在至window对象上或Vue示例上

1
2
3
4
5
6
7
8
9
http.interceptors.request.use(function (config) {
// 添加取消标记
config.cancelToken = new axios.cancelToken(cancel => {
window._axiosPromiseArr.push({ cancel })
})
return config
}, function (err) {
return Promise.reject(error)
})

在路由切换时进行操作

1
2
3
4
5
6
router.beforeEach((to, from, next) => {
window._axiosPromiseArr.forEach((ele, index) => {
ele.cancel()
delete window._axiosPromiseArr[index]
})
})

参考资料:
https://juejin.im/post/5aedd4a2f265da0b9d781b85
axios源码:https://github.com/axios/axios

本文标题:axios源码阅读笔记

文章作者:tongtong

发布时间:2019年04月15日 - 17:04

最后更新:2019年04月19日 - 20:04

原始链接:https://ilove-coding.github.io/2019/04/15/axios源码阅读笔记/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!
-------------本文结束-------------