Axios源码解析(四):核心工具方法(1)
上篇 Axios 源码解析(三):适配器 解析了适配器部分的源码。下面来解析核心工具方法的代码,本篇先看 default.js
和 /cancel
目录。
在github.com/MageeLin/ax… 中的 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
配置,如下所示
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
的源码如下:
'use strict';
var utils = require('./utils');
var normalizeHeaderName = require('./helpers/normalizeHeaderName');
var enhanceError = require('./core/enhanceError');
// 默认的content-type类型为form-urlencoded
var DEFAULT_CONTENT_TYPE = {
'Content-Type': 'application/x-www-form-urlencoded',
};
// 如果content-type没有设置,就设置content-type为value
function setContentTypeIfUnset(headers, value) {
if (
!utils.isUndefined(headers) &&
utils.isUndefined(headers['Content-Type'])
) {
headers['Content-Type'] = value;
}
}
// 获取默认的adapter
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// 浏览器使用 XHR adapter
adapter = require('./adapters/xhr');
} else if (
typeof process !== 'undefined' &&
Object.prototype.toString.call(process) === '[object process]'
) {
// nodejs使用 HTTP adapter
adapter = require('./adapters/http');
}
return adapter;
}
var defaults = {
// 以下三个options是过渡性的
transitional: {
silentJSONParsing: true,
forcedJSONParsing: true,
clarifyTimeoutError: false,
},
// 选择默认的适配器
adapter: getDefaultAdapter(),
// 请求时转化
transformRequest: [
function transformRequest(data, headers) {
// 标准化accept和content-type请求头
normalizeHeaderName(headers, 'Accept');
normalizeHeaderName(headers, 'Content-Type');
// FormData、ArrayBuffer、Buffer、Stream、File、Blob类型时,直接返回data
if (
utils.isFormData(data) ||
utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
// ArrayBuffer的View类型时,返回buffer
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
// URLSearchParam类型时,返回toString方法的结果
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(
headers,
'application/x-www-form-urlencoded;charset=utf-8'
);
return data.toString();
}
// 为其他对象时或者content-type为json时,返回stringify的结果
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;
},
],
/**
* 中止请求的超时时间(以毫秒为单位)。如果设置为 0(默认),则不会限制超时。
*/
timeout: 0,
// XSRF的配置
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
// content和body的最大长度限制
maxContentLength: -1,
maxBodyLength: -1,
// 校验状态,只有2开头的请求才是正常响应
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
},
};
// 默认headers
defaults.headers = {
common: {
Accept: 'application/json, text/plain, */*',
},
};
// 设置六种方法的Content-Type
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
请求有关,包含三个文件。
├─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 类的实例。
// Cancel.js
'use strict';
/**
* 当操作被取消时会抛出一个Cancel类的对象
*
* @class
* @param {String} message 取消的消息
*/
function Cancel(message) {
this.message = message;
}
// 给Cancel添加一个toString原型方法
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};
// 是否为Cancel类的标志
Cancel.prototype.__CANCEL__ = true;
module.exports = Cancel;
isCancel.js
在 isCancel.js 中,实现就更加简单,返回一个 isCancel
方法,通过CANCEL这个标志来判断是否为 Cancel
实例。
'use strict';
/**
* @description: 判断是否为Cancel类的对象
* @param {Any} value 要判断的值
* @return {Boolean}
*/
module.exports = function isCancel(value) {
return !!(value && value.__CANCEL__);
};
CancelToken.js
下一步就是最让人一头雾水的 CancelToken.js
,看源码之前先看官网给出的用法:
// 官方示例1
const CancelToken = axios.CancelToken;
let cancel;
axios.get('/user/12345', {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
}),
});
// 取消请求
cancel();
通过实例可以发现,这里用的就是一个 CancelToken
类,所以咱们先看最核心的 CancelToken
类:
// CancelToken.js
/**
* `CancelToken` 是被用于取消请求操作的类
*
* @class
* @param {Function} executor 执行器
*/
function CancelToken(executor) {
// 执行器的类型判断
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
// 自定义一个Promise,利用作用域拿到resolve
var resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
var token = this;
// 给执行器传参为“cancel函数”,通过闭包给到调用方
// 最终目的是将resolvePromise通过两次闭包拿到外部,给用户来自己取消
executor(function cancel(message) {
if (token.reason) {
// 如果reason属性已经有值,说明已经取消过了
return;
}
// 将取消原因组成的Cancel对象 resolve出去
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
分解为代码流:
大家可以反复理解下上面这段源码,这个地方最难的一点就是通过两层闭包
将“resolve
”最终拿给了外部。
剩下的两个原型方法就很容易理解了,一个是 throwIfRequested
,用于抛出报错对象:
// CancelToken.js
/**
* 把Cancel对象报错抛出去
*/
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
另一个是 source
,这个其实是个工厂方法,实现的原理和上面给出的官方示例1一致:
// CancelToken.js
/**
* 返回一个对象,对象包含新的"CancelToken"对象和一个函数,该函数在调用时取消"CancelToken"。
*/
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel,
};
};
当然,Axios
的官方也给出了 source
的使用方法,如下:
// 官方示例2
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,
}
);
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
总结
本篇先介绍了 default.js
中的 10
个默认属性,又将较难理解的 CancelToken
进行了详细分析。
下一篇 Axios 源码解析(四):核心工具方法(2)
来解析另外一部分 Axios
中耦合度较高的工具方法。
转载自:https://juejin.cn/post/7038815370337058824