目录
目录结构
|--lib
|-- adapters 适配node、browser环境
|-- cancel 取消请求
|-- core 核心(实例、拦截器、数据转换、异常...)
|-- helpers 辅助函数
|-- axios.js 对外提供的axios函数
|-- defaults.js 默认配置项
|-- utils.js 一些工具方法
代码分析(基于 0.19.0 版本)
- 前言
- 我们调用的 axios 函数从何而来
- axios 多种调用方式如何实现
- createInstance 函数做了什么
- 配置项
- 默认配置项
- 自定义配置项
- mergeConfig 函数如何处理默认与自定义配置
- 其它
- Axios 分析
- 构造函数
- request 方法
- 其它
- 问答
- 拦截器管理
- 拦截器有哪些功能
- 拦截器的使用
- 数据处理
- dispatchRequest 函数
- transformRequest、transformResponse
- 取消请求
- 如何配置cancelToken
- 内部取消请求的过程
- 问答
- 超时
- 如何配置超时timeout
- 总结
前言
我们调用的 axios 函数从何而来
//函数调用示例
axios({
url:'',
method:'',
....
})
// lib/axios.js
// Create the default instance to be exported
var axios = createInstance(defaults);
通过上面源码可以看到变量var axios 接收的是 createInstance 函数执行后的返回值,而我们使用的是axios()函数,说明 createInstance 函数的返回值类型是 Function.
简单理解 axios 就是一个函数.
// 示例代码
function createInstance(config) {
return function request(config) {
console.log(config);
};
}
var axios = createInstance(defaults);
axios 多种调用方式如何实现
//多种调用方式示例
axios.get();
axios.post();
axios.put();
通过以上示例代码我们知道 axios 实际上是一个函数,而在 javascript 中可以直接调用函数的方法,说明该方法是静态方法.
// 静态方法示例代码
var axios = function() {};
//静态方法声明
axios.get = function() {};
axios.post = function() {};
axios.put = function() {};
//静态方法调用
axios.get();
axios 多种调用方式其实就是调用了函数的各种静态方法.
至此我们已经大概了解 axios 函数的由来以及多种调用方式是如何实现的,接下来将深入源码了解 axios 具体的实现.
之前讲过 axios 是 createInstance 函数的返回值,那么我们就从入口开始.
createInstance 函数做了什么
//lib/axios.js
function createInstance(defaultConfig) {
// 创建Axios实例
var context = new Axios(defaultConfig);
// 使用bind生成可执行函数,将request方法在context作用域上执行;
// instance=function(){};
var instance = bind(Axios.prototype.request, context);
// 将Axios.prototype原型上所有的(属性、方法)挂载到instance(函数)上作为静态(方法、属性)供开发者使用;
// 只有这样开发者才可以直接使用别名方法如axios.get、axios.post;
// instance.get=Axios.prototype.get;
// instance.post=Axios.prototype.post
utils.extend(instance, Axios.prototype, context);
// 将context上所有的(属性、方法)挂载到instance(函数)上作为静态(方法、属性)供开发者使用;
// instance.defaults=context.defaults;
// instance.interceptors=context.interceptors;
utils.extend(instance, context);
// 开发者调用的axios其实就是instance这个函数,该函数返回值类型为Promise
return instance;
}
通过对 createInstance 函数体的分析,大体总结为以下几点:
- 创建 Axios 实例.
- 定义 instance 函数(该函数实际上就是 request 方法).
- instance 函数上挂载 Axios.prototype 原型方法作为静态方法.
- instance 函数上挂载 context 的属性、方法.
我们将基于这几点进行分析,了解过后会对 axios 有一个整体的理解.
问答
createInstance 函数直接返回 context 是否可以
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
return context;
}
由于 context 是 Axios 实例对象,var axios=context;开发者依然可以使用实例对象上的 get\post\options...等原型上定义的方法,但无法使用 axios({}),因为 createInstance 函数此刻返回的是 context 实例对象而不是一个方法,所以 axios()会直接抛错.
配置项
配置项作为 aixos 的核心之一,贯穿在初始化实例、axios 函数调用、拦截器等所以单独作为一部分讲解.
首先我们看一下默认配置项有哪些.
默认配置项
//lib/defaults.js
var defaults = {
adapter,
transformRequest,
transformResponse,
timeout,
xsrfCookieName,
xsrfHeaderName,
maxContentLength,
validateStatus,
headers: {
common: {
Accept,
delete,
get,
head,
post,
put,
patch
}
}
};
// lib/axios.js
var axios = createInstance(defaults);
创建 axios 函数时传递的形参便是默认的配置项
默认配置项一般配置常规参数,进而减少开发者对配置项不必要的修改.
自定义配置项
var defaults = {
baseUrl,
url,
method,
params,
data
};
自定义配置项通常与业务相关,如 baseUrl、url、data、params 等需要开发者进行配置.
mergeConfig 函数如何处理默认与自定义配置
//lib/core/mergeConfig.js
function mergeConfig(config1, config2){
...
}
mergeConfig 函数接收两个参数config1 config2,第一个参数为默认配置项第二个参数为自定义配置项,返回值为合并之后的 config.
在 mergeConfig 函数内主要实现了以下几点操作:
- 优先使用开发者自定义配置项-
urlmethodparamsdata
var valueFromConfig2Keys = ['url', 'method', 'params', 'data'];
utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
}
});
以上配置项,如果开发者有配置则优先级最高(直接使用).
- 按照优先级顺序(自定义>默认)取值-
headersauthproxy
var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy'];
utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) {
if (utils.isObject(config2[prop])) {
config[prop] = utils.deepMerge(config1[prop], config2[prop]);
} else if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (utils.isObject(config1[prop])) {
config[prop] = utils.deepMerge(config1[prop]);
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
以上配置项按照优先级赋值.
- 其它默认配置项赋值(自定义>默认)
var defaultToConfig2Keys = [
'baseURL',
'url',
'transformRequest',
'transformResponse',
'paramsSerializer',
'timeout',
'withCredentials',
'adapter',
'responseType',
'xsrfCookieName',
'xsrfHeaderName',
'onUploadProgress',
'onDownloadProgress',
'maxContentLength',
'validateStatus',
'maxRedirects',
'httpAgent',
'httpsAgent',
'cancelToken',
'socketPath'
];
utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
以上对其它默认配置项进行了赋值操作,如果有自定义配置则依然使用开发者自定义配置,否则使用默认.
需要注意的第 2 点中对象类型采用的是utils.deepMerge(config1[prop], config2[prop])合并,将第二个形参、第三个形参...不断的合并,总之是先使用了默认,再使用自定义配置项进行重新赋值,优先级依然是自定义>默认,而第 3 点则直接使用自定义配置项,如果不存在则使用默认(不存在合并)
- 开发者自定义属性的处理
var axiosKeys = valueFromConfig2Keys
.concat(mergeDeepPropertiesKeys)
.concat(defaultToConfig2Keys);
var otherKeys = Object.keys(config2).filter(function filterAxiosKeys(key) {
return axiosKeys.indexOf(key) === -1;
});
utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) {
if (typeof config2[prop] !== 'undefined') {
config[prop] = config2[prop];
} else if (typeof config1[prop] !== 'undefined') {
config[prop] = config1[prop];
}
});
从源码中可以看出变量 axiosKeys 是所有的配置项(默认+自定义),除此之外有可能开发者也会自定义一些私有的配置项,所以第 4 点是对除了 axios 定义的所有配置项之外的私有配置项进行处理.
其它
// axios所有配置项
var axiosKeys = [
'url',
'method',
'params',
'data',
'headers',
'auth',
'proxy',
'baseURL',
'url',
'transformRequest',
'transformResponse',
'paramsSerializer',
'timeout',
'withCredentials',
'adapter',
'responseType',
'xsrfCookieName',
'xsrfHeaderName',
'onUploadProgress',
'onDownloadProgress',
'maxContentLength',
'validateStatus',
'maxRedirects',
'httpAgent',
'httpsAgent',
'cancelToken',
'socketPath'
];
至此所有关于配置项的内容已介绍完总结如下:
- 按照优先级对配置项赋值(自定义<默认)
- 对外输出合并之后的配置项
Axios分析
构造函数
// lib/core/Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig;
//初始化request、response拦截器实例
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
构造函数内包含了配置项、初始化 request、response 拦截器实例(常用来处理登录、异常消息(后面详细介绍拦截器).
request 方法
Axios.prototype.request = function request(config) {
/**
* 如果参数config是字符串类型,则表示开发者使用的是
* 1.axios(url)
* 2.axios(url, [config])
* 这两种方式其中一种方式调用,示例如下:
* 1.axios('/user/12345')//默认get请求
* 2.axios('/user',{params:{id:12345}})
*/
if (typeof config === 'string') {
//此处处理开发者使用axios(url)这种方式调用,只传递一个参数(url),第二个参数arguments[1]为undefined的情况将config默认为对象(简单理解将参数url合并到config)
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
//合并默认配置项与开发者自定义config(将合并之后的值重新赋值给开发者自定义config)
config = mergeConfig(this.defaults, config);
//判断method类型(此处优先级 开发者自定义config>默认配置项this.defaults.method>默认get )
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
var chain = [dispatchRequest, undefined];
//将config作为参数传入Promise.resolve内,返回一个Promise对象
var promise = Promise.resolve(config);
//把request请求拦截器放入chain数组的起始位置
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
//把response响应拦截器放入chain数组结束位置
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
//依次循环数组内的request拦截器、dispatchRequest(服务请求)、response响应拦截器
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
//最终返回promise对象
return promise;
};
其它
//lib/core/Axios.js
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
})
);
};
});
从源码可以看出对外提供的多种调用方式,其实全部是挂载到原型上的方法,最终统一调用原型上的 request 方法,唯一区别是 post、put、patch 这些方法第二个参数需要传递 data 参数.
问答
构造函数做了哪些
this.defaults接收参数,初始化默认配置项this.interceptors初始化拦截对象,并且创建 request、response 拦截器的实例对象
构造函数做这两件事情目的是为 Axios.prototype.request 方法做准备.
原型方法 request 中声明
var chain变量,数组初始化为什么要有undefined
//lib/core/Axios.js
var chain = [dispatchRequest, undefined];
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
查看 promise.then 的语法,最多可以有 2 个参数.then(onFulfilled[, onRejected])
那么就不难理解 undefined 作为.then 方法的第二个参数用来作为失败的回调.
同理chain.unshift(interceptor.fulfilled, interceptor.rejected)request 请求拦截器和chain.push(interceptor.fulfilled, interceptor.rejected); response 响应拦截器向 chain 添加元素时均是新增两个元素(fulfilled、rejected)处理成功、失败的回调.
//示例代码
var chain = [
function onFulfilled(value) {
console.log(value);
},
function onRejected(value) {
console.log(value);
}
];
var promise = new Promise(function(resolve, reject) {
resolve('Success');
//or
//reject('Error');
});
promise.then(chain[0], chain[1]);
request、response 拦截器为什么分别使用
unshiftpush方法添加元素
//lib/core/Axios.js
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);
});
首先我们需要知道一个流程:发送 request 请求->等待 response 响应,所以拦截器分别在此流程开始之前(request 拦截器)和结束之后(response 拦截器).
request 拦截器->发送 request 请求->等待 response 响应->response 拦截器
按照上面的流程,对应代码
var chain = [dispatchRequest, undefined];
初始化数组已经包含了发送 request 请求->等待 response 响应此步骤(dispatchRequest),按照流程数组内还需要添加request 拦截器、response 拦截器
添加 request 拦截器
//lib/core/Axios.js
this.interceptors.request.forEach(function unshiftRequestInterceptors(
interceptor
) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
根据流程request 拦截器是在发送 request 请求之前,所以使用unshift在数组起始位置添加元素
unshift() 方法将一个或多个元素添加到数组的开头,并返回该数组的新长度(该方法修改原有数组)
//示例代码
var chain = [3, 4]; //3:请求响应
chain.unshift(1); //1:request拦截请求
//console.log(chain)1,3,4
添加 response 拦截器
//lib/core/Axios.js
this.interceptors.response.forEach(function pushResponseInterceptors(
interceptor
) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
根据流程response 拦截器是在等待 response 响应之后也就是末尾,所以直接使用push在数组末尾位置添加元素
//示例代码
var chain = [1, 3, 4];
chain.push(5); //5:response拦截请求
//console.log(chain)1,3,4,5
源码做的这一系列操作皆是让 变量 chain=[...request 拦截器,请求响应,...response 拦截器] 按照此流程添加元素.
如果在构造函数内只有一个实例对象this.interceptor=new InterceptorManager(),
无法区分请求与响应拦截器(无法实现以上流程步骤),所以this.interceptors对象即有 request 拦截器实例又有 response 拦截器实例的存在.
为什么拦截器是数组类型
无论 request、response 拦截器在实际应用场景中可能负责多个功能.
例如请求拦截器:
- request.A token 校验
- request.B loading 处理
所以this.interceptors.request、this.interceptors.response为数组类型.
while 循环如何配合 promise.then 处理流程
使用过 promise 的都知道.then 方法的返回值是 Promise 对象,因此可以使用链式调用. 如果对 Promise 感兴趣,请自行查找相关资料.
//promise.then链式调用示例代码
var promise1 = new Promise(function(resolve, reject) {
resolve({ url: '/user/12345', method: 'get' });
});
promise1
.then(function(value) {
console.log(value); //{url: "/user/12345", method: "get"}
return value;
})
.then(function(value) {
//如果上一个then方法内没有显示的 return ;则输出undefined
console.log(value); //{url: "/user/12345", method: "get"}
});
我们知道了.then 方法可以链式调用,那么 while 循环则是对(流程)数组进行.then 链式调用的具体实现.
//示例代码
var config = {
url: '/user/12345',
method: 'get',
timeout: 3000
};
//request拦截成功回调
function requestOnFulfilled(config) {
console.log('requestOnFulfilled', config);
return {...config,status:'requestOnFulfilled'};
}
//request拦截失败回调
function requestOnRejected(reason) {
console.log('requestOnRejected', reason);
return Promise.reject(reason);
}
//response拦截成功回调
function responseOnFulfilled(response) {
console.log('responseOnFulfilled', response);
return response;
}
//response拦截失败回调
function responseOnRejected(reason) {
console.log('responseOnRejected', reason);
return Promise.reject(reason);
}
//请求
function dispatchRequest(config) {
console.log('dispatchRequest', config);
return config;
}
var chain = [dispatchRequest, undefined];
chain.unshift(requestOnFulfilled, requestOnRejected);
chain.push(responseOnFulfilled, responseOnRejected);
//chain=[requestOnFulfilled,requestOnRejected,request,undefined,responseOnFulfilled,responseOnRejected];
//将config作为参数传入Promise.resolve内,返回一个Promise对象
var promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
// while循环分解
// promise
// .then(requestOnFulfilled, requestOnRejected)
// .then(dispatchRequest, undefined)
// .then(responseOnFulfilled, responseOnRejected);
shift() 方法从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度
至此我们已经对 while、promise.then 处理流程有所了解,但需要思考以下几个问题:
- 为什么循环体内 promise 需要重新赋值
promise = promise.then(chain.shift(), chain.shift());而不可以使用promise.then(chain.shift(), chain.shift()); - 把
var promise = Promise.resolve(config);修改为var promise = Promise.reject(config);requestOnRejected 函数内的return Promise.reject(reason);直接返回return reason;结果会如何.
config 配置项如何传递到拦截器内
//lib/core/Axios.js
var promise = Promise.resolve(config);
首先我们看创建 Promise 对象的同时把 config 作为参数传递到.resolve 静态方法内,目的就是为了.then 方法的成功回调可以获取到该参数. 简单理解使用.resolve 静态方法创建 Promise 对象时会把参数带入到.then 回调方法内,可查看 Promise 相关资料
//示例代码
var config = {
url: '/user/12345',
method: 'get',
timeout: 3000
};
//request拦截成功回调
function requestOnFulfilled(config) {
console.log('requestOnFulfilled', config);
return config;
}
//request拦截失败回调
function requestOnRejected(reason) {
console.log('requestOnRejected', reason);
return Promise.reject(reason);
}
var promise = Promise.resolve(config);
var chain = [requestOnFulfilled, requestOnRejected];
//此处只模拟了请求拦截器,所以未使用while循环
promise.then(chain.shift(), chain.shift());
这样一来便可以在请求拦截器内获取到 config,同时也需要注意以下几点:
- 如果没有配置拦截器,config 是直接传递到了 dispatchRequest 内
//示例代码
var config = {
url: '/user/12345',
method: 'get',
timeout: 3000
};
//请求
function dispatchRequest(config) {
console.log('dispatchRequest', config);
return config;
}
var promise = Promise.resolve(config);
var chain = [dispatchRequest, undefined];
promise.then(chain.shift(), chain.shift());
- 请求拦截器成功回调必须显示的返回 config
如果配置了请求拦截器,需要注意的是在成功回调的函数内必须显示return config,原因在于.then 的链式调用如果没有显示返回,则向下调用的函数内无法获取参数.
//示例代码
var config = {
url: '/user/12345',
method: 'get',
timeout: 3000
};
//request拦截成功回调
function requestOnFulfilled(config) {
console.log('requestOnFulfilled', config);
return config;
}
//request拦截失败回调
function requestOnRejected(reason) {
console.log('requestOnRejected', reason);
return Promise.reject(reason);
}
//请求
function dispatchRequest(config) {
console.log('dispatchRequest', config);
return config;
}
var chain = [dispatchRequest, undefined];
chain.unshift(requestOnFulfilled, requestOnRejected);
var promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
可以试一下 requestOnFulfilled 函数如果没有return config,dispatchRequest 函数的输出结果是什么.
以上示例代码 dispatchRequest 内返回的是 return config,由于只是简单的示例代码无法演示return response,其实在源码内是要返回 response 对象供向下的响应拦截器使用.
再次强调请求拦截器成功回调必须显示的返回 config 同理响应拦截器成功回调必须显示的返回 response
//lisb/core/dispatchRequest.js
function dispatchRequest(config){
...
return response;
}
以上是对 Axios 做了一个整体的了解,可以总结为以下几点:
- 构造函数
- 默认配置项
- 初始化拦截器
- request 方法
- 合并默认配置项与开发者自定义配置项
- 创建 Promise 对象
- 整合拦截器、请求(梳理流程)
- while 循环执行流程
- 返回 Promise 对象
- 多种调用方式实现
- 原型上挂载
deletegetheadoptionspostputpatch方法
- 原型上挂载
拦截器管理
拦截器有哪些功能
//lib/core/InterceptorManager.js
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
...
};
InterceptorManager.prototype.eject = function eject(id) {
...
};
InterceptorManager.prototype.forEach = function forEach(fn) {
...
};
查看拦截器的源码只有三个方法,所以接下来对三个方法一一解析.
构造函数
构造函数内比较简单,初始化 handlers 数组用来存储拦截器.
use 添加拦截器
use 方法实现添加拦截器,接收两个参数fulfilled rejected这两个参数实际上作为 Promise 中 resolve 和 reject 的回调,返回值为当前添加的元素在 handlers 内的索引位置,此处返回索引位置作为 ID 便于移除拦截器使用.
eject 移除拦截器
eject 方法实现移动拦截器,接收一个参数id,从 handlers 数组检索并置为 nullthis.handlers[id] = null,之所有这样操作避免索引位置发生变化.
forEach 循环遍历拦截器
循环遍历拦截器数组,接收一个参数fn(Function 类型)在每个拦截器上执行该函数.
拦截器的使用
//示例代码
//request拦截器
axios.interceptors.request.use(
function(config) {
// ...
return config; //显示返回config
},
function(reason) {
return Promise.reject(reason);
}
);
//response拦截器
axios.interceptors.response.use(
function(res) {
// ...
return res; //显示返回response
},
function(reason) {
return Promise.reject(reason);
}
);
数据处理
dispatchRequest
在 Axios 分析一节中介绍过该函数,用来执行流程中的发送 request 请求->等待 response 响应,接下来我们看一下具体实现了哪些功能.
//lib/core/dispatchRequest.js
throwIfCancellationRequested(config);
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
adapter(config).then(function onAdapterResolution(response) {
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
});
- 是否取消请求
throwIfCancellationRequested函数通过判断 config.cancelToken 是否存在进而取消请求 - 请求 data 数据处理 在请求之前对 config.data 进行 transformData 数据处理
- 响应 data 数据处理 adapter.then 响应之后处理 response.data 数据
在请求与响应时都会调用transformData函数,该函数接收三个参数data headers fns,函数体内循环迭代fns的每个函数fn(data, headers),最终返回data
//lib/core/transformData.js
function transformData(data, headers, fns) {
utils.forEach(fns, function transform(fn) {
data = fn(data, headers);
});
return data;
}
这就是 axios 中流程 发送 request 请求->等待 response 响应 的具体实现
在transformData函数中提到过形参fns,而这个参数传递的实参则是config.transformRequest 、config.transformResponse接下来看一下这两个参数的配置
transformRequest、transformResponse
//lib/defaults.js
transformRequest: [
function transformRequest(data, headers) {
return data;
}
];
transformResponse: [
function transformResponse(data) {
return data;
}
];
在源码中默认配置项transformRequest transformResponse的值是数组类型并且已经配置了默认处理数据的方法,所以即使我们不对这两项进行自定义配置,也依然会对调用默认函数进行数据处理.
那么我们该如何配置?
- 全局配置
因为初始化 axios 实例时,以 defaults 默认配置作为参数传递到构造函数内(可以看 Axios 分析一节),所以可以直接使用
this.defaults进行全局配置
//示例代码
//transformRequest第一种方法 在不覆盖默认配置项的前提下,通过push添加数据处理的方法(可push多项数据处理的方法)
axios.defaults.transformRequest.push(
function(data, headers) {
return data;
},
(data, headers) => {
return data;
}
);
//transformRequest第二种方法 覆盖默认配置项(数组可添加多项数据处理的方法)
axios.defaults.transformRequest = [
function(data, headers) {
return data;
},
(data, headers) => {
return data;
}
];
//transformResponse第一种方法 在不覆盖默认配置项的前提下,通过push添加数据处理的方法
axios.defaults.transformResponse.push(function(data) {
return data;
});
//transformResponse第二种方法 覆盖默认配置项
axios.defaults.transformResponse = [
function(data) {
return data;
}
];
通过对全局配置,所有 axios 发起的请求都会按照此配置进行数据处理.
- 局部配置 如果某个请求需要配置数据处理,则可以通过以下局部配置的方式实现
//示例代码
//第一种方法 在不覆盖默认配置项的前提下,post请求配置transformRequest、transformResponse数据处理
axios({
url: '',
method: 'post',
transformRequest: [
...axios.defaults.transformRequest,//将默认的配置方法通过扩展运算符拷贝过来
function(data, headers) {
...
return data;
}
],
transformResponse: [
...axios.defaults.transformResponse,
function(data) {
...
return data;
}
],
});
//第二种方法 直接对transformRequest、transformResponse进行重新定义赋值
axios({
url: '',
method: 'post',
transformRequest: [
function(data, headers) {
...
return data;
}
],
transformResponse: [
function(data) {
...
return data;
}
],
});
切记:无论全局配置或局部配置必须要显示的返回值 return data
取消请求
如何配置 cancelToken
之前讲到的配置项一节中有一项cancelToken是对取消请求的配置,有如下两种配置方式.
- 通过构造函数创建对象
//示例代码
var cancel;
axios
.get(url, {
cancelToken: new axios.CancelToken(c => {
cancel = c; //参数c则是源码中执行executor方法时对外抛出的函数方法
})
})
.then(res => {
console.log('请求成功');
})
.catch(error => {
console.log('取消请求');
});
cancel('取消');
- 使用 CancelToken.source 创建
//示例代码
var CancelToken = axios.CancelToken;
var source = CancelToken.source();
var cancel;
axios
.get(url, {
cancelToken: cancelToken: source.token
})
.then(res => {
console.log('请求成功');
})
.catch(error => {
console.log('取消请求');
});
source.cancel('取消');
第二种方式主要是为了方便开发者配置(由之前的第一种方式开发者手动创建对象变为内部创建),source方法内还是通过构造函数创建对象.
//lib/cancel/CancelToken.js
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
以上便是 axios 如何配置取消请求,以第一种配置方式为例看内部是如何操作的.
内部取消请求的过程
//lib/cancel/CancelToken.js
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);
});
}
- CancelToken 构造函数内做了什么.
- 创建
Promise对象,挂载到属性promise上,为下一步取消请求做准备. - 执行
executor函数方法——该方法是由开发者通过配置项创建CancelToken对象时传入的参数,类型必须为function.
在执行executor方法时也传递了一个参数cancel,类型为function该参数的存在是为了把对 promise 的控制权交到开发者手中,让开发者控制何时取消请求
这里对外抛出的cancel方法就是上面示例中的c,何时调用c()取消请求完全由开发者控制,方法体内执行resolvePromise方法改变Promise对象的 pending 状态.为后面使用.then 做准备
上面是取消请求的前期准备工作,接下来看何时判断是否取消请求.
- 判断是否取消请求.
//lib/adapters/xhr.js
if (config.cancelToken) {
// Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// Clean up request
request = null;
});
}
如上面的源码在 xhr.js、http.js 内创建request请求之后,会判断是否配置了config.cancelToken,如果存在则调用promise.then方法内request.abort()终止请求.
这里的config.cancelToken.promise则是上面提到过的 CancelToken 构造函数内做了什么中的第一点 创建Promise对象,挂载到属性promise上,为下一步取消请求做准备.就是为这里终止请求做的准备.
以上就是 axios 取消请求的过程(细品,细细品),大体总结为以下几点:
- 开发者配置
cancelToken(创建new axios.CancelToken()对象). - CancelToken 构造函数内创建
promise对象、执行executor方法对外抛出cancel方法 (把取消请求的控制权交给开发者). - 创建发起
request请求,判断是否存在cancelToken配置,promise.then内终止请求request.abort().
问答
配置项
cancelToken的new axios.CancelToken是从哪里来的.
// lib/axios.js
axios.CancelToken = require('./cancel/CancelToken');
在源码 axios.js 内把CancelToken直接挂载到了 axios 上,所以开发者可以直接创建实例对象new axios.CancelToken.
构造函数内如果没有
Promise是否可以.
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);
});
}
if (config.cancelToken) {
// Handle cancellation
// config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
// reject(cancel);
// Clean up request
request = null;
// });
}
- 以上是模拟把 Promise 相关的代码注释掉看会发生什么.
- 开发者配置
cancelToken——没问题. - 构造函数
创建 Promise 对象、执行executor方法对外抛出cancel取消方法(控制权依然交给开发者)——没问题. - 创建发起
request请求,判断是否存在cancelToken配置,——似乎有问题. 问题出在第 3 点,由于删除了promise.then内终止请求request.abort()promise的操作,if(config.cancelToken)内直接终止了请求,那这个取消请求的控制权就不在是开发者了(每次发送请求符合if条件便直接终止请求).
为什么
Promise决定了取消请求的控制权.
- 这个是由上面问题的第 3 点延伸出来的问题,我们反推回去看一下,能够执行
config.cancelToken.promise.then方法的前提条件一定是需要promise对象和改变promise初始化状态pending,这里才可以执行.then,那么我们就需要再次回看上面CancelToken 构造函数内做了什么那部分,发现在创建new axios.CancelToken对象时构造函数内部已经创建了 promise 对象,但状态是pending,所以构造函数内继续执行了executor方法并且对外抛出了cancel方法,而cancel方法内部resolvePromise的执行便是用来改变promise.pending状态,方便后面promise.then使用,到现在只需要关注抛出去(抛给开发者)的cancel方法,开发者什么时候调用,就什么时候改变promise.pending状态(其实上面已经介绍过内部执行流程)
超时
如何配置超时 timeout
请求超时即可以配置全局也可以配置某次请求超时时间,如下 2 个示例.
- 全局配置
// 示例代码
axios.defaults.timeout = 10000;
- 某次请求配置
// 示例代码
axios.get(url, {
timeout: 20000
});
如果即配置了全局又配置了某次请求超时的时间,则会优先使用该次请求配置,可参考配置项一节关于优先级的说明.
// lib/adapters/xhr.js
// Set the request timeout in MS
request.timeout = config.timeout;
request.ontimeout = function handleTimeout() {
var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded';
if (config.timeoutErrorMessage) {
timeoutErrorMessage = config.timeoutErrorMessage;
}
reject(createError(timeoutErrorMessage, config, 'ECONNABORTED', request));
// Clean up request
request = null;
};
上面是源码关于对请求超时的处理,相对比较简单.
总结
以上章节就是目前对 axios 的理解,还望多多指教.