目录结构
│├ lib/│ ├ adapters/│ ├ cancel/│ ├ core/│ ├ helpers/│ ├ axios.js│ ├ defaults.js // 默认配置│ └ utils.js│├ index.js // 入口复制代码
从入口开始
从入口我们可以知道axios提供了些什么
1. 从webpack.config
得知入口文件是index.js
// index.jsmodule.exports = require('./lib/axios');复制代码
从这里我们知道库的入口文件是
lib/axios.js
2. lib/axios.js
导出了什么
// lib/axios.jsmodule.exports = axios;module.exports.default = axios;复制代码
导出了 "axios" 这个对象,并且还有兼容import写法
3. 导出的axios
是啥
var defaults = require('./defaults');function createInstance(defaultConfig) { // ...}var axios = createInstance(defaults);复制代码
这里可以看出axios
是一个实例,并且使用了默认参数。
4. 导出的axios
提供了啥
a. axios.Axios
// lib/axios.jsaxios.Axios = Axios;复制代码
这里的Axios
是该实例的类
b. axios.create
// lib/axios.jsaxios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig));};复制代码
axios
还提供了给用户自己创建实例的方法,并且用户传入的config
能覆盖默认config
(mergeConfig
的逻辑)
c. axios.Cancel
、axios.CancelToken
、axios.isCancel
// lib/axios.jsaxios.Cancel = require('./cancel/Cancel');axios.CancelToken = require('./cancel/CancelToken');axios.isCancel = require('./cancel/isCancel');复制代码
axios
提供了取消请求的方法
d. axios.all
// lib/axios.jsaxios.all = function all(promises) { return Promise.all(promises);};复制代码
对Promise.all
的封装
e. axios.spread
// lib/axios.jsaxios.spread = require('./helpers/spread');复制代码
参数解构的封装,类似es6数组的...
操作符
Axios类
从入口来看,我们发现了重点方法axios.create
,并且知道它是用方法createInstance
创建实例的。
// lib/axios.jsvar Axios = require('./core/Axios');function createInstance(defaultConfig) { var context = new Axios(defaultConfig); // ...}复制代码
这里用到了Axios类,并且它是整个库的核心,所以,接下来我们看看这个类是怎么定义的。
1. 构造器
// core/Axios.jsfunction Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() };}复制代码
构造器做了两件事:i.初始化配置、ii.初始化拦截器。
2. 原型链方法
a. Axios.prototype.request
发起一个请求
发送请求,以及对请求的处理,这部分我们放在下一节详细分析。
b. Axios.prototype.getUri
获取请求完整地址
c. Axios.prototype.get
,Axios.prototype.post
...
请求的别名
- 语法糖,使得请求调用更加的语义化,更加的方便,其实现是基于
Axios.prototype.request
的 - 对于
delete
,get
,head
,options
function forEachMethodNoData(method) { Axios.prototype[method] = function(url, config) { return this.request(utils.merge(config || {}, { method: method, url: url })); };}复制代码
- 对于
post
,put
,patch
function forEachMethodWithData(method) { Axios.prototype[method] = function(url, data, config) { return this.request(utils.merge(config || {}, { method: method, url: url, data: data })); };}复制代码
3. Axios.prototype.request
这里是axios的核心实现,包含了配置合并,请求发送,拦截器加入
// core/Axios.jsAxios.prototype.request = function request(config) { if (typeof config === 'string') { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config || {}; } // ...}复制代码
对参数约定进行判断,使得调用的时候可以更灵活。 比如:axios.request('api.example.com', config)
,axios.request(config)
。
// core/Axios.jsconfig = mergeConfig(this.defaults, config);config.method = config.method ? config.method.toLowerCase() : 'get';复制代码
合并配置,并且对方法有小写处理,所以config传入不用在意大小写。
重点来了,这里不仅处理了请求,还用了一个很巧妙的方法去处理请求和拦截器,以及拦截器之间的先后顺序。
// 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;复制代码
首先执行到循环前我们看看chain
是啥结构
[请求拦截1成功,请求拦截1失败,...,dispatchRequest, undefined,响应拦截1成功,响应拦截1失败,...]复制代码
入口通过Promise.resolve(config)
将配置传入,在经历请求拦截器处理后,发起请求,请求成功获得相应,再依次经历响应拦截器处理。
dispatchRequest
发起请求的主要方法
处理URL
// core/dispatchRequest.jsif (config.baseURL && !isAbsoluteURL(config.url)) { config.url = combineURLs(config.baseURL, config.url);}复制代码
区分相对路径和绝对路径
处理请求的data
// core/dispatchRequest.js// Transform request dataconfig.data = transformData( config.data, config.headers, config.transformRequest);复制代码
处理PUT
, POST
, PATCH
传入的data
处理Headers
// core/dispatchRequest.js// Flatten headersconfig.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers || {});复制代码
这里的config已经是合并以及处理过的,这里分了三个headers
- 默认的通用headers
- 请求方法对应的headers
- 用户传入的headers
优先级依次递增,将这三个headers合并平铺到config.headers
下
紧接着删除config.headers
下非http header的属性
// core/dispatchRequest.jsutils.forEach( ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], function cleanHeaderConfig(method) { delete config.headers[method]; });复制代码
★请求适配器★
在不同环境下,发起请求的方式可能会不一样,比如:在Node环境下发起请求可能是基于http
模块,而在浏览器环境下发起请求又可能是基于XMLHttpRequest
对象
但是适配器的存在,创建了一个通用的接口,它有通用的输入输出。即不用管内部实现的差异,只需要按照约定输入,以及处理约定的输出即可。
默认适配器:
// core/dispatchRequest.jsvar defaults = require('../defaults');var adapter = config.adapter || defaults.adapter;复制代码
// defaults.jsfunction 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;}var defaults = { adapter: getDefaultAdapter() // ...}复制代码
axios
内置了两个适配器adapters/http
,adapters/xhr
,在初始化默认配置时,它判断了当前环境,并且应用了相应的适配器。
当然你也可以传入自己的适配器,并且会被优先使用。适配器可以应用于小程序开发等有自己的请求方法的场景。之后我们再来看看如何新建一个自定义适配器。
接下来是处理请求适配器的返回
// core/dispatchRequest.jsreturn 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);});复制代码
这里就是简单的处理了成功和失败情况,格式化数据。
这里的transformData
做的事就是将用户传入的config.transformResponse
全部执行一遍
throwIfCancellationRequested
后面再细讲。
4. InterceptorManager
拦截管理器,拦截器的增删,遍历
构造器
function InterceptorManager() { this.handlers = [];}复制代码
this.handlers
存的就是所有拦截器
InterceptorManager.prototype.use
// core/InterceptorManager.jsInterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected }); return this.handlers.length - 1;};复制代码
新增一个拦截器并且返回拦截器序号(id)
InterceptorManager.prototype.eject
// core/InterceptorManager.jsInterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; }};复制代码
根据序号(id)删除一个拦截器
InterceptorManager.prototype.forEach
// core/InterceptorManager.jsInterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } });};复制代码
遍历拦截器
使用
- 在
Axios
的构造函数中,初始化了两个拦截管理器
// core/Axios.jsfunction Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() };}复制代码
对于每一个Axios
实例,都可以访问自己的两个拦截管理器实例,对其进行增删
- 在
Axios.prototype.request
方法中,将拦截器全部丢到队列里执行。
请求取消
首先我们举个例子来看看如何取消请求
const CancelToken = axios.CancelToken;let cancel;axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // An executor function receives a cancel function as a parameter cancel = c; })});// cancel the requestcancel();复制代码
在lib/axios.js
中
// lib/axios.jsaxios.CancelToken = require('./cancel/CancelToken');复制代码
接下来我们看看CancelToken
类做了什么
CancelToken类
1. 构造器
// cancel/CancelToken.jsfunction 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) { // Cancellation has already been requested return; } token.reason = new Cancel(message); resolvePromise(token.reason); });}复制代码
构造器中用到了两个属性
cancelToken.promise
cancelToken.reason
构造器接收一个参数executor
,并且在构造器最后执行,传入一个cancel function
作为参数
即在例子中传入的config
{ cancelToken: new CancelToken(function executor(c) { // An executor function receives a cancel function as a parameter cancel = c;}复制代码
这里的c
引用的就是那个cancel function
这个cancel function
可以随时调用,并且在调用后,会将cancelToken.promise
reslove掉,这有什么用吗你可能会问。
// adapters/xhr.jsvar request = new XMLHttpRequest();// ...config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } request.abort(); // ...});复制代码
当调用cancel function
的时候,会终止掉请求,这就是cancel的实现原理
2. CancelToken.source
// cancel/CancelToken.jsCancelToken.source = function source() { var cancel; var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel };};复制代码
静态方法source
,自动新建cancelToken实例,并且将cancel function
绑定返回
3. CancelToken.prototype.throwIfRequested
实例方法
// cancel/CancelToken.jsCancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; }};复制代码
当取消时,抛错
在Axios.prototype.request
的dispatchRequest
中
// core/dispathRequest.jsfunction throwIfCancellationRequested(config) { if (config.cancelToken) { config.cancelToken.throwIfRequested(); }}module.exports = function dispatchRequest(config) { throwIfCancellationRequested(config); // ... return adapter(config).then(function onAdapterResolution(response) { throwIfCancellationRequested(config); // ... }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // ... } });}复制代码
在请求前后都调用了throwIfCancellationRequested
方法,在请求前取消不会发起请求,在请求后取消导致reject
Cancel类
1. 构造器
function Cancel(message) { this.message = message;}复制代码
2. 重写了toString
方法
3. Cancel.prototype.__CANCEL__
默认为true
4. 使用
// cancel/CancelToken.jstoken.reason = new Cancel(message);复制代码
token.reason
是Cancel
的实例,它表示了cancelToken的状态。而验证状态是由接下来这个方法实现的。
isCancel方法
module.exports = function isCancel(value) { return !!(value && value.__CANCEL__);};复制代码
xhrAdapter
浏览器端的请求适配器
// adapters/xhr.jsmodule.exports = function xhrAdapter(config) { return new Promise(function dispatchXhrRequest() {})})复制代码
接收配置config
作为参数,返回一个promise
dispatchXhrRequest
就是ajax的封装,来看看他是怎么封装的
- 首先,最基础的一个请求建立,发送流程
// adapters/xhr.jsvar requestData = config.data;var requestHeaders = config.headers;var request = new XMLHttpRequest();// ...request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true);// ...request.onreadystatechange = function handleLoad() {}// ...// 设置headersif ('setRequestHeader' in request) { utils.forEach(requestHeaders, function setRequestHeader(val, key) { if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { // Remove Content-Type if data is undefined delete requestHeaders[key]; } else { // Otherwise add header to the request request.setRequestHeader(key, val); } });}// ...if (requestData === undefined) { requestData = null;}request.send(requestData);复制代码
- 设置超时
// adapters/xhr.jsrequest.timeout = config.timeout;// ...request.ontimeout = function handleTimeout() { reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', request)); // Clean up request request = null;};// ...复制代码
- 上传下载进度
// adapters/xhr.jsif (typeof config.onDownloadProgress === 'function') { request.addEventListener('progress', config.onDownloadProgress);}if (typeof config.onUploadProgress === 'function' && request.upload) { request.upload.addEventListener('progress', config.onUploadProgress);}// ...复制代码
- 取消请求,之前已经提及过
// adapters/xhr.jsif (config.cancelToken) { // ...}复制代码
- 处理错误
request.onerror = function handleError() { // Real errors are hidden from us by the browser // onerror should only fire if it's a network error reject(createError('Network Error', config, null, request)); // Clean up request request = null;};复制代码
- 处理中断
request.onabort = function handleAbort() { if (!request) { return; } reject(createError('Request aborted', config, 'ECONNABORTED', request)); // Clean up request request = null;};复制代码
由于在手动触发cancel时有reject,因此这里判断当没有request的时候不重复reject
wxAdapter
在处理非预设环境时,可以自定义适配器
import axios from 'axios'const wxrequest = axios.create({ adapter: function (config) { return new Promise(function (resolve, reject) { var response = { statusText: '', config: config } var request = wx.request({ url: config.url, data: config.data, method: config.method.toUpperCase(), header: config.headers, responseType: config.responseType, success(res) { response.data = res.data response.status = res.statusCode response.headers = res.headers resolve(response) }, fail(err) { reject(err) request = null } }) response.request = request if (config.cancelToken) { config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return } request.abort() reject(cancel) request = null }) } }) }})export default wxrequest复制代码
在适配器中加入取消功能,格式化返回数据
总结
拦截器和适配器的设计,使得axios十分的灵活,更易扩展,其实现方式也值得学习使用。