之前在老东家一直用的是 gRPC
和 graphQL
,最近跳槽去了新单位,用回了传统的 Restful
接口那一套,所以又得重新捡起 Axios
来。除了毕业前自己写项目时用过 Axios
,后来就再也没碰过,所以刚一上手非常不习惯,甚至 data
和 params
的配置都混了,浪费了很多无谓的时间。给自己个规划,空闲时间把 Axios
的源码全都捋顺了输出一波。
解析之前我先从Axios 源代码仓库 fork
了一份:https://github.com/MageeLin/axios-source-code-analysis 。
fork 的时间是 2021 年 8 月 4 日,commitID
是 e9965bfafc82d8b42765705061b9ebe2d5532493
。解析时从 master
分支拉了一个新分支 analysis
。
已完成/进行中:
- Axios 源码解析(零):文档翻译
- Axios 源码解析(一):模块分解
- Axios 源码解析(二):通用工具方法
- Axios 源码解析(三):适配器
- Axios 源码解析(四):核心工具方法(1)
- Axios 源码解析(五):核心工具方法(2)
- Axios 源码解析(六):入口文件
- Axios 源码解析(七):项目周边
文档翻译
照理说官网已经有了文档,我不应该再翻译一遍。但是自己捋下来发现官网的中文文档有部分错误和缺失,所以自己再把文档人肉翻译一下,顺便加深印象。
正文
基于promise的、可以用于浏览器和node.js的网络请求库
新的 axios 文档地址: 点击此处
内容表
特性
浏览器支持
|
|
|
|
|
|
Latest ✔ |
Latest ✔ |
Latest ✔ |
Latest ✔ |
Latest ✔ |
11 ✔ |
安装
使用 npm:
使用 bower:
使用 yarn:
使用 jsDelivr CDN:
1
| <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
使用 unpkg CDN:
1
| <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
实例
注意: CommonJS 用法
为了在CommonJS中使用 require()
导入时获得TypeScript类型推断(智能感知/自动完成),请使用以下方法:
1 2 3
| const axios = require('axios').default;
|
发起一个 GET
请求
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
| const axios = require('axios');
axios.get('/user?ID=12345') .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }) .then(function () { });
axios.get('/user', { params: { ID: 12345 } }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }) .then(function () { });
async function getUser() { try { const response = await axios.get('/user?ID=12345'); console.log(response); } catch (error) { console.error(error); } }
|
注意: 由于async/await
是ECMAScript 2017中的一部分,而且在IE和一些旧的浏览器中不支持,所以使用时务必要小心。
发起一个 POST
请求
1 2 3 4 5 6 7 8 9 10
| axios.post('/user', { firstName: 'Fred', lastName: 'Flintstone' }) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); });
|
发起多个并发请求
1 2 3 4 5 6 7 8 9 10 11 12 13
| function getUserAccount() { return axios.get('/user/12345'); }
function getUserPermissions() { return axios.get('/user/12345/permissions'); }
Promise.all([getUserAccount(), getUserPermissions()]) .then(function (results) { const acct = results[0]; const perm = results[1]; });
|
axios API
可以向 axios
传递相关配置来创建请求
axios(config)
1 2 3 4 5 6 7 8 9
| axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } });
|
1 2 3 4 5 6 7 8 9
| axios({ method: 'get', url: 'http://bit.ly/2mTM3nY', responseType: 'stream' }) .then(function (response) { response.data.pipe(fs.createWriteStream('ada_lovelace.jpg')) });
|
axios(url[, config])
请求方法别名
为了方便起见,已经为所有支持的请求方法提供了别名。
- axios.request(config)
- axios.get(url[, config])
- axios.delete(url[, config])
- axios.head(url[, config])
- axios.options(url[, config])
- axios.post(url[, data[, config]])
- axios.put(url[, data[, config]])
- axios.patch(url[, data[, config]])
注意
在使用别名方法时, 配置中url
、method
、data
这些属性都不必指定。
创建一个实例
你可以使用自定义配置新建一个实例。
axios.create([config])
1 2 3 4 5
| const instance = axios.create({ baseURL: 'https://some-domain.com/api/', timeout: 1000, headers: {'X-Custom-Header': 'foobar'} });
|
实例方法
以下是可用的实例方法。新指定的配置将与上述实例的配置合并。
- axios#request(config)
- axios#get(url[, config])
- axios#delete(url[, config])
- axios#head(url[, config])
- axios#options(url[, config])
- axios#post(url[, data[, config]])
- axios#put(url[, data[, config]])
- axios#patch(url[, data[, config]])
- axios#getUri([config])
请求配置
以下是创建请求时可以用的配置选项。只有 url
是必需的。如果没有指定 method
,请求将默认使用 GET
方法。
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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
| { url: '/user',
method: 'get',
baseURL: 'https://some-domain.com/api/',
transformRequest: [function (data, headers) {
return data; }],
transformResponse: [function (data) {
return data; }],
headers: {'X-Requested-With': 'XMLHttpRequest'},
params: { ID: 12345 },
paramsSerializer: function (params) { return Qs.stringify(params, {arrayFormat: 'brackets'}) },
data: { firstName: 'Fred' }, data: 'Country=Brasil&City=Belo Horizonte',
timeout: 1000,
withCredentials: false,
adapter: function (config) { },
auth: { username: 'janedoe', password: 's00pers3cret' },
responseType: 'json',
responseEncoding: 'utf8',
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
onUploadProgress: function (progressEvent) { },
onDownloadProgress: function (progressEvent) { },
maxContentLength: 2000,
maxBodyLength: 2000,
validateStatus: function (status) { return status >= 200 && status < 300; },
maxRedirects: 5,
socketPath: null,
httpAgent: new http.Agent({ keepAlive: true }), httpsAgent: new https.Agent({ keepAlive: true }),
proxy: { protocol: 'https', host: '127.0.0.1', port: 9000, auth: { username: 'mikeymike', password: 'rapunz3l' } },
cancelToken: new CancelToken(function (cancel) { }),
decompress: true
transitional: { silentJSONParsing: true;
forcedJSONParsing: true; clarifyTimeoutError: false; } }
|
响应结构
一个请求的响应包含以下信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| { data: {},
status: 200,
statusText: 'OK',
headers: {},
config: {},
request: {} }
|
当使用 then
时,将接收如下响应:
1 2 3 4 5 6 7 8
| axios.get('/user/12345') .then(function (response) { console.log(response.data); console.log(response.status); console.log(response.statusText); console.log(response.headers); console.log(response.config); });
|
当使用 catch
,或者传递一个 rejection callback 作为 then
的第二个参数时,响应可以通过 error
对象来获得,详情请见 错误处理 章节。
默认配置
你可以指定默认配置,它将作用于每个请求中。
全局默认值
1 2 3 4 5 6 7
| axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
|
自定义实例默认值
1 2 3 4 5 6 7
| const instance = axios.create({ baseURL: 'https://api.example.com' });
instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
|
配置的优先级
配置将会按优先级进行合并。优先级顺序如下:在 lib/defaults.js 中找到的库默认值,然后是实例的 defaults
属性,最后是请求的 config
参数。后面的优先级要高于前面的。下面有一个实例。
1 2 3 4 5 6 7 8 9 10 11
| const instance = axios.create();
instance.defaults.timeout = 2500;
instance.get('/longRequest', { timeout: 5000 });
|
拦截器
在请求或响应被 then 或 catch 处理前拦截它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| axios.interceptors.request.use(function (config) { return config; }, function (error) { return Promise.reject(error); });
axios.interceptors.response.use(function (response) { return response; }, function (error) { return Promise.reject(error); });
|
如果稍后需要移除拦截器,可以如下所示:
1 2
| const myInterceptor = axios.interceptors.request.use(function () {}); axios.interceptors.request.eject(myInterceptor);
|
可以给自定义的 axios 实例添加拦截器。
1 2
| const instance = axios.create(); instance.interceptors.request.use(function () {});
|
当添加请求拦截器时,默认情况下它们为异步执行。所以当主线程被阻塞时,可能会导致 axios 请求的延迟执行(在后台为拦截器创建了一个promise,并且被放在调用堆栈的底部)。如果想让请求拦截器同步执行,可以向 options 对象添加一个 synchronous 标志,该标志将使 axios 同步运行代码并避免请求执行中的任何延迟。
1 2 3 4
| axios.interceptors.request.use(function (config) { config.headers.test = 'I am only a header!'; return config; }, null, { synchronous: true });
|
如果想要基于运行时检查来执行特定拦截器,可以向 options 对象添加一个 runWhen
函数。 当且仅当 runWhen
的返回值为 true
时,拦截器才会被执行。该函数将调用 config 对象(也可以将自定义的参数绑定)。当只需要在特定规则下运行的异步请求拦截器时,这会很方便。
1 2 3 4 5 6 7
| function onGetCall(config) { return config.method === 'get'; } axios.interceptors.request.use(function (config) { config.headers.test = 'special get headers'; return config; }, null, { runWhen: onGetCall });
|
错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| axios.get('/user/12345') .catch(function (error) { if (error.response) { console.log(error.response.data); console.log(error.response.status); console.log(error.response.headers); } else if (error.request) { console.log(error.request); } else { console.log('Error', error.message); } console.log(error.config); });
|
使用 validateStatus
配置选项,可以自定义什么样的 HTTP code 会抛出错误。
1 2 3 4 5
| axios.get('/user/12345', { validateStatus: function (status) { return status < 500; } })
|
使用 toJSON
可以获取更多关于HTTP错误的信息。
1 2 3 4
| axios.get('/user/12345') .catch(function (error) { console.log(error.toJSON()); });
|
取消请求
使用 cancel token 可以取消一个请求。
Axios 的 cancel token API 是基于 cancelable promises proposal 提案。
可以使用 CancelToken.source
工厂方法创建一个 cancel token ,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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.');
|
也可以通过给 CancelToken
的构造函数传递一个 executor 函数参数来创建一个 cancel token:
1 2 3 4 5 6 7 8 9 10 11 12
| const CancelToken = axios.CancelToken; let cancel;
axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { cancel = c; }) });
cancel();
|
注意: 可以使用同一个 cancel token 取消多个请求。
如果在发送 Axios 请求时已经取消了cancel token,则该请求会立即取消,而不会尝试发出任何实际请求。
请求体编码
默认情况下,axios 将 JavaScript 对象序列化为 JSON
格式 。 如果要换成application/x-www-form-urlencoded
格式发送数据,可以使用以下选项之一。
浏览器
在浏览器中,可以使用URLSearchParams
API,如下所示:
1 2 3 4
| const params = new URLSearchParams(); params.append('param1', 'value1'); params.append('param2', 'value2'); axios.post('/foo', params);
|
请注意,不是所有的浏览器(参见 caniuse.com)都支持 URLSearchParams
,但是可以使用polyfill (确保 polyfill 在全局环境生效)
或者可以使用qs
库编码数据:
1 2
| const qs = require('qs'); axios.post('/foo', qs.stringify({ 'bar': 123 }));
|
或者用另一种方式 (ES6):
1 2 3 4 5 6 7 8 9
| import qs from 'qs'; const data = { 'bar': 123 }; const options = { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, data: qs.stringify(data), url, }; axios(options);
|
Node.js
Query string
在 node.js 中, 可以使用 querystring
模块,如下所示:
1 2
| const querystring = require('querystring'); axios.post('http://something.com/', querystring.stringify({ foo: 'bar' }));
|
或者从‘url 模块’中使用‘URLSearchParams’,如下所示:
1 2 3
| const url = require('url'); const params = new url.URLSearchParams({ foo: 'bar' }); axios.post('http://something.com/', params.toString());
|
也可以使用 qs
库。
注意
如果需要对嵌套对象进行字符串化处理,则最好使用 qs
库,因为 querystring 方法在该用例中存在已知问题(https://github.com/nodejs/node-v0.x-archive/issues/1665)。
在 node.js 中可以使用 form-data
库,如下所示:
1 2 3 4 5 6 7 8
| const FormData = require('form-data'); const form = new FormData(); form.append('my_field', 'my value'); form.append('my_buffer', new Buffer(10)); form.append('my_file', fs.createReadStream('/foo/bar.jpg'));
axios.post('https://example.com', form, { headers: form.getHeaders() })
|
或者使用一个拦截器:
1 2 3 4 5 6
| axios.interceptors.request.use(config => { if (config.data instanceof FormData) { Object.assign(config.headers, config.data.getHeaders()); } return config; });
|
版本号
在 axios 发布 1.0
版本之前,不兼容的API更改将随新的次要版本一起发布。例如0.5.1
和0.5.4
将具有相同的API,但0.6.0
会有向后不兼容的API变化。
Promises
axios 依赖于原生的 ES6 Promise 实现支持。如果你的环境不支持 ES6 Promises,可以使用 polyfill来兼容。
TypeScript
axios 包含了 TypeScript 类型定义和 axios errors 的类型守卫。
1 2 3 4 5 6 7 8 9 10 11
| let user: User = null; try { const { data } = await axios.get('/user?ID=12345'); user = data.userDetails; } catch (error) { if (axios.isAxiosError(error)) { handleAxiosError(error); } else { handleUnexpectedError(error); } }
|
在线一键安装
你可以使用 Gitpod (一个在线的开源免费的 IDE)来对项目进行贡献或运行实例。
资源
灵感
axios 深受 Angular 中提供的 $http 服务 的启发。axios 致力于提供一个独立的、在 Angular 之外使用的、类似 $http 的服务。
许可
MIT
文档翻译完成,下一节从模块分解开始入手解析代码。