axios.js的实战经验

总结一下使用Axios.js的遇到的注意事项,如有不对欢迎指正。

本文不是一篇分析源码的文章,如果需要看源码解析看这里axios实例应用及源码剖析 - xhr篇 (走心教程),写的非常不错。

axios.js的流程图

先放张图镇楼,后续的问题需要多看看这张图。

来自走心教程
来自走心教程

axios.js版本问题

之前没有注意官网说明,后来升级了版本发现报错,立刻把官网的文档翻了一下,发现Semver章节有描述,

直到v1.0之前,当发布一个minor版本,就代表有breaking changes了。所以升级有危险啊!!!

拦截器interceptor

拦截器是axios.js的核心了,就是有了拦截器做解耦,才能把代码组合的更优雅。

interceptor的返回值类型

当初在写拦截器时发现,官方给的例子,interceptor的返回值既可以是普通的value值,又可以是Promise。当时就疑惑了,有啥区别???

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 官方例子:
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});

// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});

具体细节看源码分析走心篇

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 源码里面就有一行代码
promise = promise.then(chain.shift(), chain.shift());
// 其实把上面例子翻译一下就是这样的
// Add a request interceptor
promise = promise.then(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});

// Add a response interceptor
promise = promise.then(function (response) {
// Do something with response data
return response;
}, function (error) {
// Do something with response error
return Promise.reject(error);
});

是不是瞬间就明白了拦截器的return值,其实可以是普通的value值,或者Promise对象。

返回Promise就有很多想法了,我们可以在拦截器interceptor中做异步操作,终于可以为所欲为了 :)。

interceptor的执行顺序

这个问题呢,是我在使用过程中纠结的一个问题,我想做一个功能,就是当Content-Type: application/x-www-form-urlencoded;charset=utf-8时候,自动把data使用qs.stringify(后来知道这个功能其实使用transformer更合适)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// axios/lib/core/Axios.js
// 核心逻辑就是这段
var chain = [dispatchRequest, undefined]; // 正中间的是发送请求的拦截器
var promise = Promise.resolve(config);

// request 拦截器unshift,添加到数组的头部
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
// response 拦截器 push到数组尾部
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
chain.push(interceptor.fulfilled, interceptor.rejected);
});

while (chain.length) {
// 循环执行拦截器
promise = promise.then(chain.shift(), chain.shift());
}

上述代码其实就是做了两件事情:

  • 把拦截器放入数组chain中

    1. this.interceptors.request拦截器是从chain的头部放入;this.interceptors.request其实也是数组,所以request数组中最后放入的interceptor却在chain数组的最前面,和axios.interceptors.request.use时候的顺序相反。
    2. this.interceptors.response拦截器是放入chain的尾部,所以interceptor顺序和添加axios.interceptors.response.use时候的顺序相同
  • 循环执行chain中的拦截器

    request拦截器,最后添加的,最先执行

    response拦截器,按照添加的顺序执行

注意:如果你的interceptors 之间顺序有先后依赖关系,需要特别注意。

transformer的使用

transformRequesttransformResponse都属于Axios.js的transformer,官方宣传的Automatic transforms for JSON data
就是通过transformResponse实现的。官方文档写的太简单了,没用写要注意的问题。

内置的transformRequest和transformResponse

默认处理JSON

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
// axios/lib/defaults.js
var defaults = {
adapter: getDefaultAdapter(),

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;
}],
};

1
2
3
4
5
6
7
8
9
// transformRequest
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);
}
  • 如果data是URLSearchParams类型,axios把Content-Type修改为application/x-www-form-urlencoded;charset=utf-8,且序列化data
  • 如果data是Object,axios把Content-Type修改为application/json;charset=utf-8,且序列化data
  • transformResponse默认尝试解析JSON

内置transformRequest和transformResponse被覆盖

内置的transformRequesttransformResponse,很容易被覆盖,如果你不需要内置的那就很好办,如果你需要就需要注意下。

1
2
3
4
5
6
7
import axios from 'axios';
const defaults = { // 需要把默认的添加回来
transformRequest: [].concat(axios.defaults.transformRequest, (data, headers) => {
// do something
return data;
})
};

自动序列化data参数且修改Content-Type

注意URLSearchParams兼容性问题,如果兼容IE 11,使用qs处理下。

  • 如果data是URLSearchParams类型,axios把Content-Type修改为application/x-www-form-urlencoded;charset=utf-8,且序列化data
  • 如果data是Object,axios把Content-Type修改为application/json;charset=utf-8,且序列化data

transformer和interceptor的区别

transformer和interceptor都能在请求过程中发挥作用,有啥区别呢?

区别:

  1. transformer和interceptor的执行的时机不同,看上图
  2. transformer主要针对data(虽然也能直接改变header,但是不建议)
  3. transformer只能同步,而interceptor可以执行异步操作
1
2
3
4
5
6
7
8
module.exports = function transformData(data, headers, fns) {
/*eslint no-param-reassign:0*/
utils.forEach(fns, function transform(fn) {
data = fn(data, headers);
});

return data;
};

baseUrl配置

axios内部判断config.url,如果是绝对路径开始就不添加baseUrl,否则就添加

1
2
3
4
5
// axios/lib/core/dispatchRequest.js
// Support baseURL config
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}

二进制数据注意

如果你的transformerinterceptordata有变形或者使用,一定要先判断二进制,不然容易导致报错。
推荐的做法就是,使用单独的axios实例处理二进制数据
多个axios实例之间,注意共享headers部分(这个坑,我已经踩进去了)。

总结

以上是本人使用axios.js过程中遇到的疑惑,然后通过查看源码的总结,如果对你有帮助,我会感到很荣幸。

axios.js相关资源

axios-retry
axios-cache-adapter