原文连接node
一直在使用axios库,在享受它带来的便利的同时,总感受不读读它的源码有点对不起它,恰好网上介绍axios源码的文章较少,因此写下这篇文章,权当抛砖引玉。ios
axios是同构的JavaScript的异步请求库,它能够在浏览器端和NodeJS环境里使用。git
VueJS的做者尤大也推荐这个工具,它除了异步请求网络资源功能,还有以下功能:github
axios的GitHub地址。axios
在axios中,使用适配器设计模式来屏蔽平台的差别性,让使用者能够在浏览器端和NodeJS环境中使用同一套API发起http请求。设计模式
axios的默认配置里的adapter是经过getDefaultAdapter()
方法来获取的,它的逻辑以下:数组
function getDefaultAdapter() {
var adapter;
// Only Node.JS has a process variable that is of [[Class]] process
if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// For node use HTTP adapter
adapter = require('./adapters/http');
} else if (typeof XMLHttpRequest !== 'undefined') {
// For browsers use XHR adapter
adapter = require('./adapters/xhr');
}
return adapter;
}
复制代码
如上面代码,经过判断运行环境的特征来选择不一样的API发起http请求。promise
接下来分别介绍这两个文件——http和xhr。浏览器
这个文件里,引用了NodeJS的http和https库,用于发出http请求,并使用Promise接收请求结果。网络
代码的细节不介绍了,就讲个大概的思路,咱们都知道发起http请求,最重要的是遵照http协议,书写正确的请求头,而axios就是经过传入config
接收使用者的一些定制参数,其中包括请求头,请求参数等等,而后在内部使用(http/https).request(options, callback)发起http请求。
具体如何整合、处理传入的参数,还请下载源码看看。
相似http的逻辑,只不过是调用了WebAPI的XMLHTTPRequest接口发起http请求。
axios提供了拦截器的功能,能够在请求发起前处理传入的config或者其它操做,也能够在接收完响应后处理response。
咱们能够看看Axios的构造函数,很简单:
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
复制代码
其中的InterceptorManager维护一个数组,用以收集拦截器函数,有fulfilled
和rejected
,分别对应Promise的onSuccess和onFail的回调,接下来看看拦截器和发起http请求是如何结合在一块儿的,咱们看看Axios的原型上的request方法:
Axios.prototype.request = function request(config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof config === 'string') {
config = arguments[1] || {};
config.url = arguments[0];
} else {
config = config || {};
}
config = mergeConfig(this.defaults, config);
config.method = config.method ? config.method.toLowerCase() : 'get';
// Hook up interceptors middleware
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;
};
复制代码
从上面能够看出,它们结合的方式是使用Promise把拦截器和发起http请求的操做结合起来的,interceptors.request
会安排在发起http请求的操做前,interceptors.response
会安排在发起http请求的操做后。
axios提供了观察上传和下载进度的功能,不过仅支持在浏览器环境中,核心代码以下:
// Handle progress if needed
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
// Not all browsers support upload events
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
复制代码
从上面能够看出,下载进度回调其实就是监听XMLHTTPRequest对象的progress事件,上传进度回调其实就是XMLHTTPRequest对象的upload属性的progress事件。
官方文档里指出这个功能须要开发者返回一个Promise对象而且在Promise里返回一个有效的Response对象:
// `adapter` allows custom handling of requests which makes testing easier.
// Return a promise and supply a valid response (see lib/adapters/README.md).
adapter: function (config) {
/* ... */
}
复制代码
咱们能够在源码中找到这个功能的实现方式:
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// Transform response data
response.data = transformData(
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData(
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
复制代码
从上面能够看出,若是咱们在使用axios发出http请求时,若是传入的config对象有adapter属性,这个属性会顶替了默认的adapter(NodeJS的http.request()或XMLHTTPRequest),因此咱们须要在config的adapter属性中返回一个Promise,而且这个Promise会返回一个有效的Response对象。
axios提供了一个功能,能够自定义报错的响应码的范围,能够经过config.validateStatus
来配置。
默认的范围是200到300之间:
validateStatus: function validateStatus(status) {
return status >= 200 && status < 300;
}
复制代码
而在源码中,这个方法是经过lib\core\settle.js
来调用的:
module.exports = function settle(resolve, reject, response) {
var validateStatus = response.config.validateStatus;
if (!validateStatus || validateStatus(response.status)) {
resolve(response);
} else {
reject(createError(
'Request failed with status code ' + response.status,
response.config,
null,
response.request,
response
));
}
};
复制代码
从上面能够看出,settle的入参很像Promise的resolve和reject,接下来,咱们看看settle又是在哪里被调用的。
果不其然,在lib\adapters\http.js
和lib\adapters\xhr.js
中都看到settle的身影。
细节就不说了,我大体说一下思路,就是axios使用Promise发起http请求后,会把传入Promise对象的函数中的resolve和reject再次传递给settle中,让它来决定Promise的状态是onResolved仍是onRejected。
axios官方文档指出axios提供了取消已经发出去的请求的功能。
The axios cancel token API is based on the withdrawn cancelable promises proposal.
上面引用的话里指出这是一个promise的提议,不过已经被撤回了。
在这里,笔者想说的是,其实不依赖这个提议,咱们也能够写一个简单取消请求的功能,只要你熟悉闭包就能够了。
思路是这样的:咱们可使用闭包的方式维护一个是否取消请求的状态,而后在处理Promise的onResolved回调的时候判断一下这个状态,若是状态是须要取消请求的话,就reject结果,大体以下:
function dispatchRequest(config) {
let hasCancled = false;
return Promise((resolve, reject) => {
if (hasCancled) {
reject({ hasCancled: true })
} else {
/** 处理正常响应 **/
}
})
.then(/** 其余业务操做 **/)
.catch(err => {
if (err.hasCancled) {
/** 处理请求被取消 */
}
})
}
复制代码
最后,咱们能够大体了解了axios强大的背后缘由:使用适配器模式屏蔽了平台差别性,并提供统一的API,使用Promise的链式调用来保证整个请求过程的有序性和加强一些额外的功能。
axios库是一个很精美的第三库,值得咱们去读读它的源码。你也会收获不少的。很感谢你能坚持看到这里。
不只文章里提到的,还有好几个有趣的课题值得大伙去研究,好比: