上篇 Axios 源码解析(三):适配器 解析了适配器部分的源码。下面来解析核心工具方法的代码,本篇先看 default.js
和 /cancel
目录。
在https://github.com/MageeLin/axios-source-code-analysis 中的 analysis
分支可以看到当前已解析完的文件。
default.js
default.js
文件中,最终导出了 10
个变量和方法,顾名思义,这 10
个导出的内容全都是为默认功能
服务的:
transitional
: 默认过渡 option
提示标志(之后的版本会被废弃)
adapter
: 默认的适配器
transformRequest
: 默认的请求转换器
transformResponse
: 默认的响应转换器
timeout
: 默认超时时间(0
代表无限制)
xsrfCookieName
: 默认 XSRF
的 cookie
配置
xsrfHeaderName
: 默认 XSRF
的 header
配置
maxContentLength
: 默认 content
最大长度限制
maxBodyLength
: 默认 body
最大长度限制
validateStatus
: 默认状态校验器(200-300
才被看作为正常响应)
headers
: 默认的 header
配置,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| defaults.headers = { common: { Accept: 'application/json, text/plain, */*', }, delete: {}, get: {}, head: {}, post: { 'Content-Type': 'application/x-www-form-urlencoded', }, put: { 'Content-Type': 'application/x-www-form-urlencoded', }, patch: { 'Content-Type': 'application/x-www-form-urlencoded', }, };
|
default.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 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
| 'use strict';
var utils = require('./utils'); var normalizeHeaderName = require('./helpers/normalizeHeaderName'); var enhanceError = require('./core/enhanceError');
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; } }
function getDefaultAdapter() { var adapter; if (typeof XMLHttpRequest !== 'undefined') { adapter = require('./adapters/xhr'); } else if ( typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]' ) { adapter = require('./adapters/http'); } return adapter; }
var defaults = { transitional: { silentJSONParsing: true, forcedJSONParsing: true, clarifyTimeoutError: false, },
adapter: getDefaultAdapter(),
transformRequest: [ function transformRequest(data, headers) { normalizeHeaderName(headers, 'Accept'); 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) || (headers && headers['Content-Type'] === 'application/json') ) { setContentTypeIfUnset(headers, 'application/json'); return JSON.stringify(data); } return data; }, ],
transformResponse: [ function transformResponse(data) { var transitional = this.transitional; var silentJSONParsing = transitional && transitional.silentJSONParsing; var forcedJSONParsing = transitional && transitional.forcedJSONParsing; var strictJSONParsing = !silentJSONParsing && this.responseType === 'json';
if ( strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length) ) { try { return JSON.parse(data); } catch (e) { if (strictJSONParsing) { if (e.name === 'SyntaxError') { throw enhanceError(e, this, 'E_JSON_PARSE'); } throw e; } } }
return data; }, ],
timeout: 0,
xsrfCookieName: 'XSRF-TOKEN', xsrfHeaderName: 'X-XSRF-TOKEN',
maxContentLength: -1, maxBodyLength: -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;
|
/cancel 目录
/cancel
目录的内容与主动取消 Axios
请求有关,包含三个文件。
1 2 3 4
| ├─cancel // 取消请求 │ Cancel.js │ CancelToken.js │ isCancel.js
|
Axios
的 cancel token API
是基于 TC39
的 cancelable promises proposal 提议封装的。使用时需要在配置项中手动添加 cancelToken 属性,通过执行回调函数的参数形式来实现“取消”功能。
源码分析
这块的源代码看着非常不直观,先从依赖最少的两个文件入手:
Cancel.js
首先 Cancel.js
中,实现了一个 Cancel
类,这个类有一个实例属性 message
,一个原型方法 toString
来组装并返回 message
,一个原型属性CANCEL来判别是否为 Cancel 类的实例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 'use strict';
function Cancel(message) { this.message = message; }
Cancel.prototype.toString = function toString() { return 'Cancel' + (this.message ? ': ' + this.message : ''); };
Cancel.prototype.__CANCEL__ = true;
module.exports = Cancel;
|
isCancel.js
在 isCancel.js 中,实现就更加简单,返回一个 isCancel
方法,通过CANCEL这个标志来判断是否为 Cancel
实例。
1 2 3 4 5 6 7 8 9 10
| 'use strict';
module.exports = function isCancel(value) { return !!(value && value.__CANCEL__); };
|
CancelToken.js
下一步就是最让人一头雾水的 CancelToken.js
,看源码之前先看官网给出的用法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const CancelToken = axios.CancelToken; let cancel;
axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { cancel = c; }), });
cancel();
|
通过实例可以发现,这里用的就是一个 CancelToken
类,所以咱们先看最核心的 CancelToken
类:
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
|
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) { return; }
token.reason = new Cancel(message); resolvePromise(token.reason); }); }
|
分解为代码流:

大家可以反复理解下上面这段源码,这个地方最难的一点就是通过两层闭包
将“resolve
”最终拿给了外部。
剩下的两个原型方法就很容易理解了,一个是 throwIfRequested
,用于抛出报错对象:
1 2 3 4 5 6 7 8 9
|
CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; } };
|
另一个是 source
,这个其实是个工厂方法,实现的原理和上面给出的官方示例1一致:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
CancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel, }; };
|
当然,Axios
的官方也给出了 source
的使用方法,如下:
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
| 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 { } });
axios.post( '/user/12345', { name: 'new name', }, { cancelToken: source.token, } );
source.cancel('Operation canceled by the user.');
|
总结
本篇先介绍了 default.js
中的 10
个默认属性,又将较难理解的 CancelToken
进行了详细分析。
下一篇 Axios 源码解析(四):核心工具方法(2)
来解析另外一部分 Axios
中耦合度较高的工具方法。