- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第19期,链接:juejin.cn/post/708311…
axios源码学习
axios是什么
axios是一个很优秀的基于promise的HTTP库,可以应用于浏览器端和node端
axios可以干什么
(1)支持promise API。
(2)拦截请求与响应,比如:在请求前添加授权和响应前做一些事情。
(3)转换请求数据和响应数据,比如:进行请求加密或者响应数据加密。
(4)取消请求。
(5)自动转换JSON数据。
(6)客户端支持防御XSRF。
上述两条是百度查的,在我看来就是http请求库,xmlhttprequest的封装。
axios的目录结构
├── /dist/ # 项目输出目录
├── /lib/ # 项目源码目录
│ ├── /cancel/ # 定义取消功能
│ ├── /core/ # 一些核心功能
│ │ ├── Axios.js # axios的核心主类
│ │ ├── dispatchRequest.js # 用来调用http请求适配器方法发送请求
│ │ ├── InterceptorManager.js # 拦截器构造函数
│ │ └── settle.js # 根据http响应状态,改变Promise的状态
│ ├── /helpers/ # 一些辅助方法
│ ├── /adapters/ # 定义请求的适配器 xhr、http
│ │ ├── http.js # 实现http适配器
│ │ └── xhr.js # 实现xhr适配器
│ ├── axios.js # 对外暴露接口
│ ├── defaults.js # 默认配置
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置TypeScript的声明文件
└── index.js # 入口文件
源码分析
看源码先看README.md和package.json,这是基础常识。 axios是有很大一部分的东西都是基于promise的,所以建议深入学习一下promise(liubin.org/promises-bo…), 这样有利于了解源码里面的细节。
入口文件:
// index.js
import axios from './lib/axios.js';
export default axios;
./lib/axios.js是源码的入口:
// 代码结构
'use strict';
// 引入很多工具函数和一些核心的功能方法
import utils from './utils.js';
import bind from './helpers/bind.js';
import Axios from './core/Axios.js';
import mergeConfig from './core/mergeConfig.js';
import defaults from './defaults/index.js';
import formDataToJSON from './helpers/formDataToJSON.js';
import CanceledError from './cancel/CanceledError.js';
import CancelToken from'./cancel/CancelToken.js';
import isCancel from'./cancel/isCancel.js';
import {VERSION} from './env/data.js';
import toFormData from './helpers/toFormData.js';
import AxiosError from './core/AxiosError.js';
import spread from './helpers/spread.js';
import isAxiosError from './helpers/isAxiosError.js';
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
*
* @returns {Axios} A new instance of Axios
*/
// 此处是生成并返回了一个function
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig);
const instance = bind(Axios.prototype.request, context);
// Copy axios.prototype to instance
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
// Copy context to instance
utils.extend(instance, context, null, {allOwnKeys: true});
// Factory for creating new instances
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// Create the default instance to be exported
const axios = createInstance(defaults);
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// 将对应的方法全部挂到axios这个方法上
// Expose Cancel & CancelToken
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
axios.toFormData = toFormData;
// Expose AxiosError class
axios.AxiosError = AxiosError;
// alias for CanceledError for backward compatibility
axios.Cancel = axios.CanceledError;
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
axios.spread = spread;
// Expose isAxiosError
axios.isAxiosError = isAxiosError;
axios.formToJSON = thing => {
return formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing);
};
export default axios;
能够实现axios的多种使用方式的核心是createInstance方法(需要结合./core/Axios.js文件阅读更加清晰):
// 主要是创建一个Axios实例,最终会被作为对象导出
function createInstance(defaultConfig) {
// 创建一个Axios实例
const context = new Axios(defaultConfig);
// 以下代码也可以这样实现:var instance = Axios.prototype.request.bind(context);
// 这样instance就指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用
// Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用
const instance = bind(Axios.prototype.request, context);
// 把Axios.prototype上的方法扩展到instance对象上,
// 这样 instance 就有了 get、post、put等方法
// 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
// 把context对象上的自身属性和方法扩展到instance上
// 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性
// 这样,instance 就有了 defaults、interceptors 属性。
utils.extend(instance, context, null, {allOwnKeys: true});
// 一般情况,项目使用默认导出的axios实例就可以满足需求了,
// 如果不满足需求需要创建新的axios实例,axios包也预留了接口
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
以上代码看上去很绕,其实createInstance最终是希望拿到一个Function,这个Function指向Axios.prototype.request,这个Function还会携带有Axios.prototype上的每个方法作为静态方法,且这些方法的上下文都是指向同一个对象
Axios、Axios.prototype.request的源码
Axios是axios包的核心,一个Axios实例就是一个axios应用,其他方法都是对Axios内容的扩展
而Axios构造函数的核心方法是request方法,各种axios的调用方式最终都是通过request方法发请求的
// /lib/core/Axios.js
class Axios {
constructor(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
// 所有请求都是调用它来发起
request(configOrUrl, config) {
// 省略
}
getUri(config) {
config = mergeConfig(this.defaults, config);
const fullPath = buildFullPath(config.baseURL, config.url);
return buildURL(fullPath, config.params, config.paramsSerializer);
}
}
// 此处及下面主要是为了实现axios.get,axios.post等方法
// 为支持的请求方法提供别名
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(utils.merge(config || {}, {
method: method,
url: url,
data: data
}));
};
});
axios.prototype.request
这里的代码比较复杂,需要对promise有一些较深的理解才可以,先上代码然后简单的分析一下,后续对拦截器,dispatchRequest做详细的解释。
request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
// 前面主要是对参数的判断,转换,校验等
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
config = mergeConfig(this.defaults, config);
const {transitional, paramsSerializer} = config;
if (transitional !== undefined) {
validator.assertOptions(transitional, {
silentJSONParsing: validators.transitional(validators.boolean),
forcedJSONParsing: validators.transitional(validators.boolean),
clarifyTimeoutError: validators.transitional(validators.boolean)
}, false);
}
if (paramsSerializer !== undefined) {
validator.assertOptions(paramsSerializer, {
encode: validators.function,
serialize: validators.function
}, true);
}
// Set config.method
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
// Flatten headers
const defaultHeaders = config.headers && utils.merge(
config.headers.common,
config.headers[config.method]
);
defaultHeaders && utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
config.headers = new AxiosHeaders(config.headers, defaultHeaders);
// 这里是重点的开始
// filter out skipped interceptors
const requestInterceptorChain = [];
let synchronousRequestInterceptors = true;
// 将所有的请求拦截器加入到requestInterceptorChain数组中
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
const responseInterceptorChain = [];
// 将所有的回应拦截器加入到responseInterceptorChain数组中
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
// 如果有请求拦截器的时候将会进入到if语句中
if (!synchronousRequestInterceptors) {
// chain中放入的dispatchRequest是真正的请求
const chain = [dispatchRequest.bind(this), undefined];
// 在dispatchRequest之前放入请求拦截器
chain.unshift.apply(chain, requestInterceptorChain);
// 在dispatchRequest之后放入回应拦截器
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
promise = Promise.resolve(config);
// 此处开始依次执行请求拦截器,请求,回应拦截器的对应函数
while (i < len) {
promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
// 如果没有请求拦截器将会进入到如下的执行中
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
// 存在请求拦截器的时候将会执行请求拦截器,然后try catch中器执行真正的请求
while (i < len) {
const onFulfilled = requestInterceptorChain[i++];
const onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
try {
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error);
}
// 存在回应拦截器的时候将会执行,与请求拦截器类似
i = 0;
len = responseInterceptorChain.length;
while (i < len) {
promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
}
return promise;
}
拦截器
拦截器主要分为请求与回应拦截器,分别是针对请求前与回应后的数据进行拦截修改。
首先是如何添加拦截器,删除拦截器的:
// 添加请求拦截器
const myRequestInterceptor = axios.interceptors.request.use(config => {
// 在发送http请求之前做些什么
return config; // 有且必须有一个config对象被返回
}, error => {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(response => {
// 对响应数据做点什么
return response; // 有且必须有一个response对象被返回
}, error => {
// 对响应错误做点什么
return Promise.reject(error);
});
// 移除某次拦截器
axios.interceptors.request.eject(myRequestInterceptor);
下面是拦截器的源码:
class Axios {
constructor(instanceConfig) {
// 每个axios实例都有一个interceptors实例属性,
// interceptors对象上有两个属性request、response。
// 这两个属性都是一个InterceptorManager实例
// 而这个InterceptorManager构造函数就是用来管理拦截器的
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
}
InterceptorManager构造函数:
InterceptorManager构造函数就是用来实现拦截器的,这个构造函数原型上有3个方法:use、eject、forEach。 关于源码,其实是比较简单的,都是用来操作该构造函数的handlers实例属性的
'use strict';
import utils from './../utils.js';
class InterceptorManager {
constructor() {
// 存放拦截器方法,数组内每一项都是有两个属性的对象,两个属性分别对应成功和失败后执行的函数。
this.handlers = [];
}
/**
* Add a new interceptor to the stack
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
*/
// 往拦截器里添加拦截方法
use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled,
rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
}
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*
* @returns {Boolean} `true` if the interceptor was removed, `false` otherwise
*/
// 用来注销指定的拦截器
eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
}
/**
* Clear all interceptors from the stack
*
* @returns {void}
*/
clear() {
if (this.handlers) {
this.handlers = [];
}
}
/**
* Iterate over all the registered interceptors
*
* This method is particularly useful for skipping over any
* interceptors that may have become `null` calling `eject`.
*
* @param {Function} fn The function to call for each interceptor
*
* @returns {void}
*/
// 遍历this.handlers,并将this.handlers里的每一项作为参数传给fn执行
forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
}
}
export default InterceptorManager;
axios内部又是怎么让这些拦截器能够在请求前、请求后拿到我们想要的数据的呢?
(一定要熟悉promise的链式执行才可以明白下面的执行逻辑,否则可能会不明白何时发送的请求) 还记得之前的/lib/core/Axios.js文件吗,下面就回顾一下具体的流程:首先使用foreach函数将对应的拦截器放入对应的数组,然后声明一个chain,里面放入真正的ajax请求(dispatchRequest.bind(this)),将请求拦截放入dispatchRequest的前面,回应拦截放入dispatchRequest的后面,然后开始使用promise的链式执行依次执行函数。
const requestInterceptorChain = [];
let synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
if (!synchronousRequestInterceptors) {
const chain = [dispatchRequest.bind(this), undefined];
chain.unshift.apply(chain, requestInterceptorChain);
chain.push.apply(chain, responseInterceptorChain);
// 添加了拦截器后的chain数组大概会是这样的:
// [
// requestFulfilledFn, requestRejectedFn, ...,
// dispatchRequest, undefined,
// responseFulfilledFn, responseRejectedFn, ....,
// ]
len = chain.length;
promise = Promise.resolve(config);
// 数组的 shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值。
// 每次执行while循环,从chain数组里按序取出两项,并分别作为promise.then方法的第一个和第二个参数
// 按照我们使用InterceptorManager.prototype.use添加拦截器的规则,正好每次添加的就是我们通过InterceptorManager.prototype.use方法添加的成功和失败回调
// 通过InterceptorManager.prototype.use往拦截器数组里添加拦截器时使用的数组的push方法,
// 对于请求拦截器,从拦截器数组按序读到后是通过unshift方法往chain数组数里添加的,又通过shift方法从chain数组里取出的,所以得出结论:对于请求拦截器,先添加的拦截器会后执行
// 对于响应拦截器,从拦截器数组按序读到后是通过push方法往chain数组里添加的,又通过shift方法从chain数组里取出的,所以得出结论:对于响应拦截器,添加的拦截器先执行
// 第一个请求拦截器的fulfilled函数会接收到promise对象初始化时传入的config对象,而请求拦截器又规定用户写的fulfilled函数必须返回一个config对象,所以通过promise实现链式调用时,每个请求拦截器的fulfilled函数都会接收到一个config对象
// 第一个响应拦截器的fulfilled函数会接受到dispatchRequest(也就是我们的请求方法)请求到的数据(也就是response对象),而响应拦截器又规定用户写的fulfilled函数必须返回一个response对象,所以通过promise实现链式调用时,每个响应拦截器的fulfilled函数都会接收到一个response对象
// 任何一个拦截器的抛出的错误,都会被下一个拦截器的rejected函数收到,所以dispatchRequest抛出的错误才会被响应拦截器接收到。
// 因为axios是通过promise实现的链式调用,所以我们可以在拦截器里进行异步操作,而拦截器的执行顺序还是会按照我们上面说的顺序执行,也就是 dispatchRequest 方法一定会等待所有的请求拦截器执行完后再开始执行,响应拦截器一定会等待 dispatchRequest 执行完后再开始执行。
while (i < len) {
promise = promise.then(chain[i++], chain[i++]);
}
return promise;
}
上面已经讲述了拦截器那么到底位于中游的dispatchRequest是如何发送http请求的呢?
dispatchRequest
dispatchRequest主要做了3件事:
1,拿到config对象,对config进行传给http请求适配器前的最后处理;
2,http请求适配器根据config配置,发起请求
3,http请求适配器请求完成后,如果成功则根据header、data、和config.transformResponse拿到数据转换后的response,并return。
export default function dispatchRequest(config) {
throwIfCancellationRequested(config);
config.headers = AxiosHeaders.from(config.headers);
// 对请求data进行转换
config.data = transformData.call(
config,
config.transformRequest
);
// http请求适配器会优先使用config上自定义的适配器,没有配置时才会使用默认的XHR或http适配器,不过大部分时候,axios提供的默认适配器是能够满足我们的
const adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// Transform response data
response.data = transformData.call(
config,
config.transformResponse,
response
);
response.headers = AxiosHeaders.from(response.headers);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
config.transformResponse,
reason.response
);
reason.response.headers = AxiosHeaders.from(reason.response.headers);
}
}
return Promise.reject(reason);
});
}
看了上面的代码,我们知道其实dispatchRequest调用adapter,那么adapter是怎么工作的呢?
adapter
dispatchRequest方法会调用xhrAdapter或者是httpAdapter方法(就以xhrAdapter为例),xhrAdapter方法返回的是还一个Promise对象
// /lib/adapters/xhr.js
export default function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
// ... 省略代码
});
};
xhrAdapter内的XHR发送请求成功后会执行这个Promise对象的resolve方法,并将请求的数据传出去,
反之则执行reject方法,并将错误信息作为参数传出去。
下面是xhr的主要代码(不完整):
// /lib/adapters/xhr.js
var request = new XMLHttpRequest();
function onloadend() {
if (!request) {
return;
}
// Prepare the response
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
if ('onloadend' in request) {
// Use onloadend if available
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
// The request errored out and we didn't get a response, this will be
// handled by onerror instead
// With one exception: request that using file: protocol, most browsers
// will return status as 0 even though it's a successful request
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
setTimeout(onloadend);
};
}
request.onerror = function handleError() {
reject(/**/);
request = null;
};
request.ontimeout = function handleTimeout() {
reject(/**/);
request = null;
};
验证服务端的返回结果是否通过验证:
// /lib/core/settle.js
export default function settle(resolve, reject, response) {
const validateStatus = response.config.validateStatus;
if (!response.status || !validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(new AxiosError(
'Request failed with status code ' + response.status,
[AxiosError.ERR_BAD_REQUEST, AxiosError.ERR_BAD_RESPONSE][Math.floor(response.status / 100) - 4],
response.config,
response.request,
response
));
}
}
回到dispatchRequest方法内,首先得到xhrAdapter方法返回的Promise对象,
然后通过.then方法,对xhrAdapter返回的Promise对象的成功或失败结果再次加工,
成功的话,则将处理后的response返回,
失败的话,则返回一个状态为rejected的Promise对象
return adapter(config).then(function onAdapterResolution(response) {
// ...
return response;
}, function onAdapterRejection(reason) {
// ...
return Promise.reject(reason);
});
那么至此,用户调用axios()方法时,就可以直接调用Promise的.then或.catch进行业务处理了。
回过头来,我们在介绍dispatchRequest一节时说到的数据转换,而axios官方也将数据转换专门作为一个亮点来介绍的,那么数据转换到底能在使用axios发挥什么功效呢?
数据转换器-转换请求与响应数据
如何使用呢?
1. 修改全局的转换器
// 往现有的请求转换器里增加转换方法
axios.defaults.transformRequest.push((data, headers) => {
// ...处理data
return data;
});
// 重写请求转换器
axios.defaults.transformRequest = [(data, headers) => {
// ...处理data
return data;
}];
// 往现有的响应转换器里增加转换方法
axios.defaults.transformResponse.push((data, headers) => {
// ...处理data
return data;
});
// 重写响应转换器
axios.defaults.transformResponse = [(data, headers) => {
// ...处理data
return data;
}];
2. 修改某次axios请求的转换器
// 往已经存在的转换器里增加转换方法
axios.get(url, {
// ...
transformRequest: [
...axios.defaults.transformRequest, // 去掉这行代码就等于重写请求转换器了
(data, headers) => {
// ...处理data
return data;
}
],
transformResponse: [
...axios.defaults.transformResponse, // 去掉这行代码就等于重写响应转换器了
(data, headers) => {
// ...处理data
return data;
}
],
})
了解了使用,我们接下来进行源码的分析:
// 默认的defaults配置项里已经自定义了一个请求转换器和一个响应转换器
const defaults = {
transformRequest: [
function transformRequest(data, headers) {
normalizeHeaderName(headers, 'Content-Type');
// ...
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) {
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch (e) { /* Ignore */ }
}
return data;
}
],
}
那么在axios项目里,是在什么地方使用了转换器呢?
请求转换器的使用地方是http请求前,使用请求转换器对请求数据做处理,
然后传给http请求适配器使用。
// /lib/core/dispatchRequest.js
// 代码分为两段
config.data = transformData.call(
config,
config.transformRequest
);
// 响应转换器的使用地方是在http请求完成后,根据http请求适配器的返回值做数据转换处理
response.data = transformData.call(
config,
config.transformResponse,
response
);
看下transformData方法: 主要遍历转换器数组,分别执行每一个转换器,根据data和headers参数,返回新的data
// /lib/core/transformData.js
export default function transformData(fns, response) {
const config = this || defaults;
const context = response || config;
const headers = AxiosHeaders.from(context.headers);
let data = context.data;
utils.forEach(fns, function transform(fn) {
data = fn.call(config, data, headers.normalize(), response ? response.status : undefined);
});
headers.normalize();
return data;
}
看完了之后,知道在何处使用了转换器,并且是如何使用的,那转换器和拦截器的关系?从上面看其实功能都差不多,甚至都不需要进行区分,完全是可以混用的。 但是依据我自己阅读源码,我感觉在请求时,拦截器主要负责修改config配置项,数据转换器主要负责转换请求体,比如转换对象为字符串,在请求响应后,拦截器可以拿到response,数据转换器主要负责处理响应体,比如转换字符串为对象。
header设置
首先看看如何使用header的:
import axios from 'axios'
// 设置通用header
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; // xhr标识
// 设置某种请求的header
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
// 设置某次请求的header
axios.get(url, {
headers: {
'Authorization': 'whr1',
},
})
接下来看看这一块源码是如何实现的:
// lib/core/Axios.js文件中的request函数中实现
// Flatten headers
const defaultHeaders = config.headers && utils.merge(
config.headers.common,
config.headers[config.method]
);
如何取消已经发送的请求
如何使用取消
import axios from 'axios'
// 第一种取消方法
axios.get(url, {
cancelToken: new axios.CancelToken(cancel => {
if (/* 取消条件 */) {
cancel('取消日志');
}
})
});
// 第二种取消方法
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(url, {
cancelToken: source.token
});
source.cancel('取消日志');
看过了使用方法之后,可以看一下源码是怎么来进行的:
// lib/cancel/CancelToken
class CancelToken {
constructor(executor) {
// 判断函数
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
let resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
const token = this;
// eslint-disable-next-line func-names
this.promise.then(cancel => {
if (!token._listeners) return;
let i = token._listeners.length;
while (i-- > 0) {
token._listeners[i](cancel);
}
token._listeners = null;
});
// eslint-disable-next-line func-names
this.promise.then = onfulfilled => {
let _resolve;
// eslint-disable-next-line func-names
const promise = new Promise(resolve => {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
executor(function cancel(message, config, request) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new CanceledError(message, config, request);
resolvePromise(token.reason);
});
}
// 其余代码省略
static source() {
let cancel;
const token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token,
cancel
};
}
}
// 真正调用取消的是在/lib/adapters/xhr.js中使用的
if (config.cancelToken) {
config.cancelToken.unsubscribe(onCanceled);
}
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
取消功能的核心是通过CancelToken内的this.promise = new Promise(resolve => resolvePromise = resolve), 得到实例属性promise,此时该promise的状态为pending通过这个属性,在/lib/adapters/xhr.js文件中执行,从而改变此promise的状态;
在CancelToken外界,通过executor参数拿到对cancel方法的控制权,这样当执行cancel方法时就可以改变实例的promise属性的状态为rejected,从而执行request.abort()方法达到取消请求的目的。
上面第二种写法可以看作是对第一种写法的完善,因为很多是时候我们取消请求的方法是用在本次请求方法外 如下:
// 例如,发送A、B两个请求,当B请求成功后,取消A请求。
// 第1种写法:
let source;
axios.get(Aurl, {
cancelToken: new axios.CancelToken(cancel => {
source = cancel;
})
});
axios.get(Burl).then(() => source('B请求成功了'));
// 第2种写法:
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get(Aurl, {
cancelToken: source.token
});
axios.get(Burl).then(() => source.cancel('B请求成功了'));
// 相对来说,我更推崇第1种写法,因为第2种写法太隐蔽了,不如第一种直观好理解。
总结
其实还是有很多方面没有写出来,包含跨域携带cookie,超时配置及处理等,但是原理都基本包含在上面的源码中,只要把上面的东西都理解了基本后续的也就迎刃而解了,axios这个项目里,有很多对JS使用很巧妙的地方,比如对promise的串联操作(当然你也可以说这块是借鉴很多异步中间件的处理方式),让我们可以很方便对请求前后的各种处理方法的流程进行控制;很多实用的小优化,比如请求前后的数据处理,省了程序员一遍一遍去写JSON.xxx了;同时支持了浏览器和node两种环境,对使用node的项目来说无疑是极好的,最主要的还是要理解promise和XMLHttpRequest,那么这个工程基本也就理解了。关于理解工具函数,可能是平时对应lodash工具库看的比较多,对于axios里面的工具函数比较的无感。
// promsie链式理解
var a = Promise.resolve(1);
var b = a.then(function (value) {
console.log(value);
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(value);
}, 3000)
})
})
var c = b.then(function (value) {
console.log('3后获取到value:1');
})