axios 是如何封装 HTTP 请求的

作者:微信小助手

发布时间:2019-10-14T23:01:46

(给前端大全加星标,提升前端技能

英文:Alex 译文:zhangbao90s

https://juejin.im/post/5d906269f265da5ba7451b02

概述

前端开发中,经常会遇到发送异步请求的场景。一个功能齐全的 HTTP 请求库可以大大降低我们的开发成本,提高开发效率。

axios 就是这样一个 HTTP 请求库,近年来非常热门。目前,它在 GitHub 上拥有超过 40,000 的 Star,许多权威人士都推荐使用它。

因此,我们有必要了解下 axios 是如何设计,以及如何实现 HTTP 请求库封装的。撰写本文时,axios 当前版本为 0.18.0,我们以该版本为例,来阅读和分析部分核心源代码。axios 的所有源文件都位于 lib 文件夹中,下文中提到的路径都是相对于 lib 来说的。

本文我们主要讨论:

  • 怎样使用 axios。

  • axios 的核心模块(请求、拦截器、撤销)是如何设计和实现的?

  • axios 的设计优点是什么?

如何使用 axios

要理解 axios 的设计,首先需要看一下如何使用 axios。我们举一个简单的例子来说明下 axios API 的使用。

发送请求

axios({
method:'get',
url:'http://bit.ly/2mTM3nY',
responseType:'stream'
})
.then(function(response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});

这是一个官方示例。从上面的代码中可以看到,axios 的用法与 jQuery 的 ajax 方法非常类似,两者都返回一个 Promise 对象(在这里也可以使用成功回调函数,但还是更推荐使用 Promise 或 await),然后再进行后续操作。

这个实例很简单,不需要我解释了。我们再来看看如何添加一个拦截器函数。

添加拦截器函数

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);
});

从上面的代码,我们可以知道:发送请求之前,我们可以对请求的配置参数(config)做处理;在请求得到响应之后,我们可以对返回数据做处理。当请求或响应失败时,我们还能指定对应的错误处理函数。

撤销 HTTP 请求

在开发与搜索相关的模块时,我们经常要频繁地发送数据查询请求。一般来说,当我们发送下一个请求时,需要撤销上个请求。因此,能撤销相关请求功能非常有用。axios 撤销请求的示例代码如下:

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
cancelToken: source.token
}).catch(function(thrown) {
if (axios.isCancel(thrown)) {
console.log('请求撤销了', thrown.message);
} else {
}
});

axios.post('/user/12345', {
name: '新名字'
}, {
cancelToken: source.token
}).

source.cancel('用户撤销了请求');

从上例中可以看到,在 axios 中,使用基于 CancelToken 的撤销请求方案。然而,该提案现已撤回,详情如 点这里。具体的撤销请求的实现方法,将在后面的源代码分析的中解释。

axios 核心模块的设计和实现

通过上面的例子,我相信每个人都对 axios 的使用有一个大致的了解了。下面,我们将根据模块分析 axios 的设计和实现。下面的图片,是我在本文中会介绍到的源代码文件。如果您感兴趣,最好在阅读时克隆相关的代码,这能加深你对相关模块的理解。

HTTP 请求模块

请求模块的代码放在了 core/dispatchRequest.js 文件中,这里我只展示了一些关键代码来简单说明:

module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
var adapter = config.adapter || defaults.adapter;

return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
return Promise.reject(reason);
});
};

上面的代码中,我们能够知道 dispatchRequest 方法是通过 config.adapter ,获得发送请求模块的。我们还可以通过传递,符合规范的适配器函数来替代原来的模块(一般来说,我们不会这样做,但它是一个松散耦合的扩展点)。

在 defaults.js 文件中,我们可以看到相关适配器的选择逻辑——根据当前容器的一些独特属性和构造函数,来确定使用哪个适配器。

function getDefaultAdapter() {
var adapter;
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
adapter =