日期:2018-9-1 00:00 | 标签: | 阅读:119
之前都没有翻译过成篇的文章,这次借阮博士的分享的这篇文章,小小的实践一下,所以难免有很多疏漏,得好好加油,继续努力。
原文地址: How to Implement an HTTP Request Library with Axios
背景
在前端开发过程中,我们经常会遇到发送异步请求的场景,而使用 HTTP 库可以很大程度上减少我们开发成本以及提高开发效率。
Axios 近年来普遍使用的 HTTP 库,如今,在 github 上有奖金 4 万个 star,很多大牛也在极力推广使用这个库。 所以,我们有必要了解 Axios 是如何设计的,当作者发布这篇文章的时候,Axios 的最新版本是 0.18.0
,文章里的代码示例和分析也会基于这个版本,它的源代码位于 lib/
目录下,文中代码的引用路径也是基于这个文件夹。
这篇文章将涵盖以下内容
- 如何使用 Axios
- 它的核心模块(requests, interceptors, withdrawals) 是如何设计和实现的
- Axios 的优势是什么
如何使用 Axios
为了理解 Axios 的设计,我们首先得学会怎么使用,以下是几个简单的使用示例
发送请求
axios({
method:'get',
url:'http://bit.ly/2mTM3nY',
responseType:'stream'
})
.then(function(response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
});
这个是官方的示例代码,我们可以看到,其使用起来跟 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);
})
从上述代码,我们可以知道,我们可以在请求正式发送前修改配置参数,也可以在请求返回后对数据执行特定的操作,当然,我们也可以对错误进行处理。
取消 HTTP 请求
当我们开发查询相关的模块时,我们经常需要频繁的发起请求,通常情况下,我们需要在发起新的请求前取消掉上一次发起的请求,这无疑是 axios 的一个优势,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('Request canceled', thrown.message);
} else {
// 错误处理
}
});
axios.post('/user/12345', {
name: 'new name'
}, {
cancelToken: source.token
})
// 取消请求
source.cancel('Operation canceled by the user.');
从上诉代码我们可以看到有一个叫作 CancelToken
的变量,借助其可以实现请求取消,不过这种方式已经被官方舍弃了,具体原因我们稍后会提到。
axios 核心模块时如何设计实现的?
通过上述案例分析,大家已经清楚如何使用 axios 了,接下来,我们会分析 axios 核心模块的设计与实现方式,下述图片涵盖了这篇文章中有分析到的模块,如果你有兴趣,也可以直接从 github 上下载 axios 源码研究,能够让你对相关模块有更深的了解。
HTTP request 模块
request 模块位于 core/dispatchReqeust.js
文件. 这里我选取了相关片段做一个简要介绍
module.exports = function dispatchRequest(config) {
throwIfCancellationRequested(config);
// 其它代码(略)
// 这个适配器能够自动根据环境选择发起请求的方式,选择 Node 或 XHR
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
来实现请求发送的,我们也可以通过传一个符合规范的适配器方法替换原生的模块( 当然,我们并不是真的这么做,只是说这种解耦方式还是不错的 )
在 default.js 相关文件中, 我们可以看到适配器的选择逻辑,其是根据一些特殊变量以及当前容器的一些构造函数来进行判断的。
function getDefaultAdapter() {
var adapter;
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// Node 端
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
// 浏览器端
adapter = require('./adapters/xhr');
}
return adapter;
}
Interceptor 模块
现在,让我们来看看 axios 是如何实现请求和返回的拦截器方法的,先让我们看看公布的相关接口,以 request
函数为例
Axios.prototype.request = function request(config) {
// 其它代码
var chain = [dispatchRequest, undefined];
var promise = Promise.resolve(config);
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);
});
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
};
这个函数是 axios 发送请求的接口,因为函数的实现比较长,这里我只做简要的说明
chain 是执行队列,初始值是带有配置参数的 Promise ,在chain 队列里,第一个值是 dispatchReqeust,其用来发送请求,第二值是 undefined,相对于 dispatchReqeust,那么 undefined 的作用是什么呢?其主要是为了作为 Promise 中成功和失败回调函数的默认值,从代码 promise = promise.then(chain.shift(), chain.shift());
中我们也可以看到,因此 dispatchReqeust 和 undefined 可以看做是一对函数。
在chain 队列里,dispatchReqeust 函数用于发送请求,在此之前,使用 unshift
插入了一个请求拦截器,在此之后,使用 push 在后方插入了一个,需要注意的是,这两个拦截器一般是成对出现的。
从上述代码,我们粗略的知道了拦截器的使用,接下来,我们学习下如何取消请求。
Cancel-request 模块
请求取消模块代码位于 Cancel/
文件夹, 让我们看看其中的关键代码。
首先,让我们瞧一瞧 Cancel
这个类,这个类主要用于记录取消状态,具体代码如下
function Cancel(message) {
this.message = message;
}
Cancel.prototype.toString = function toString() {
return 'Cancel' + (this.message ? ': ' + this.message : '');
};
Cancel.prototype.__CANCEL__ = true;
在 CancelToken 中,其通过传递一个 Promise 对象实现了 HTTP 请求取消 ,具体代码如下
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) {
// cancle 方法已经调用了
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
CancelToken.source = function source() {
var cancel;
var token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel
};
};
相应的 cancel-request 代码位于 adapter/xhr.js
文件中
if (config.cancelToken) {
// 等待 cancel 方法调用
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// 重置请求
request = null;
});
}
通过对以上代码的分析,我们简要总结下取消请求代码的实现逻辑
- 在需要取消的请求中,source 方法返回了
CancelToken
类的实例以及调用了cancle
方法进行初始化。 当 source 方法返回实例 A 时,一个处于 pending 状态的 promise 也被创建了,当把实例 A 传入 axis 后,这个 promise 就被用来做取消请求的触发器了。 当 source 方法返回的 cancel 方法被调用时,这个 promise 的状态就会由 pending 转为 fulfilled,then 回调函数也会立即执行,至此,执行取消逻辑的脚本request.abort()
也会被触发
axios 的优势是什么
发送请求
如前面所说,axios 并没有特殊对待请求发送方法 dispatchRequest
,而是把其放到了队列的中间来保证处理的一致性,同时也提高了代码的可读性。
适配器
在适配器处理逻辑中,http 和 xhr 模块并没有嵌入到 dispatchRequest 中,而是在 default.js 文件中通过配置方法进行引入,这不仅让模块很好的进行解耦,也使得未来用户自定义自己的适配器成为可能。
取消请求
在取消请求逻辑中,axios 使用 Promise 作为触发器,传递 resolve 函数到回调函数中作为外部参数,这不仅保证了内部逻辑的一致性,也保证了在取消发送请求时,不需要直接改变相关类的数据,从而很大程度上避免了侵入其它模块。
总结
这篇文章详细介绍了 axios 的使用、设计思想以及实现方法,阅读后,你能够知道 axios 是如何设计的以及了解到模块之间的封装与交互。
当然,这篇文章也仅仅是介绍了核心模块的相关的内容,如果你对其它代码有兴趣,可以直接去 github阅读相关代码。
有任何问题和建议,欢迎留言。