目录结构
│
├ lib/
│ ├ adapters/
│ ├ cancel/
│ ├ core/
│ ├ helpers/
│ ├ axios.js
│ ├ defaults.js // 默认配置
│ └ utils.js
│
├ index.js // 入口
从入口开始
从入口我们可以知道axios提供了些什么
1. 从webpack.config
得知入口文件是index.js
// index.js
module.exports = require('./lib/axios');
从这里我们知道库的入口文件是
lib/axios.js
2. lib/axios.js
导出了什么
// lib/axios.js
module.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.js
axios.Axios = Axios;
这里的Axios
是该实例的类
b. axios.create
// lib/axios.js
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
axios
还提供了给用户自己创建实例的方法,并且用户传入的config
能覆盖默认config
(mergeConfig
的逻辑)
c. axios.Cancel
、axios.CancelToken
、axios.isCancel
// lib/axios.js
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios
提供了取消请求的方法
d. axios.all
// lib/axios.js
axios.all = function all(promises) {
return Promise.all(promises);
};
对Promise.all
的封装
e. axios.spread
// lib/axios.js
axios.spread = require('./helpers/spread');
参数解构的封装,类似es6数组的...
操作符
Axios类
从入口来看,我们发现了重点方法axios.create
,并且知道它是用方法createInstance
创建实例的。
// lib/axios.js
var Axios = require('./core/Axios');
function createInstance(defaultConfig) {
var context = new Axios(defaultConfig);
// ...
}
这里用到了Axios类,并且它是整个库的核心,所以,接下来我们看看这个类是怎么定义的。
1. 构造器
// core/Axios.js
function 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.js
Axios.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.js
config = 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.js
if (config.baseURL && !isAbsoluteURL(config.url)) {
config.url = combineURLs(config.baseURL, config.url);
}
区分相对路径和绝对路径
处理请求的data
// core/dispatchRequest.js
// Transform request data
config.data = transformData(
config.data,
config.headers,
config.transformRequest
);
处理PUT
, POST
, PATCH
传入的data
处理Headers
// core/dispatchRequest.js
// Flatten headers
config.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.js
utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
★请求适配器★
在不同环境下,发起请求的方式可能会不一样,比如:在Node环境下发起请求可能是基于http
模块,而在浏览器环境下发起请求又可能是基于XMLHttpRequest
对象
但是适配器的存在,创建了一个通用的接口,它有通用的输入输出。即不用管内部实现的差异,只需要按照约定输入,以及处理约定的输出即可。
默认适配器:
// core/dispatchRequest.js
var defaults = require('../defaults');
var adapter = config.adapter || defaults.adapter;
// defaults.js
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;
}
var defaults = {
adapter: getDefaultAdapter()
// ...
}
axios
内置了两个适配器adapters/http
,adapters/xhr
,在初始化默认配置时,它判断了当前环境,并且应用了相应的适配器。
当然你也可以传入自己的适配器,并且会被优先使用。适配器可以应用于小程序开发等有自己的请求方法的场景。之后我们再来看看如何新建一个自定义适配器。
接下来是处理请求适配器的返回
// core/dispatchRequest.js
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);
});
这里就是简单的处理了成功和失败情况,格式化数据。
这里的transformData
做的事就是将用户传入的config.transformResponse
全部执行一遍
throwIfCancellationRequested
后面再细讲。
4. InterceptorManager
拦截管理器,拦截器的增删,遍历
构造器
function InterceptorManager() {
this.handlers = [];
}
this.handlers
存的就是所有拦截器
InterceptorManager.prototype.use
// core/InterceptorManager.js
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};
新增一个拦截器并且返回拦截器序号(id)
InterceptorManager.prototype.eject
// core/InterceptorManager.js
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
根据序号(id)删除一个拦截器
InterceptorManager.prototype.forEach
// core/InterceptorManager.js
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
遍历拦截器
使用
- 在
Axios
的构造函数中,初始化了两个拦截管理器
// core/Axios.js
function 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 request
cancel();
在lib/axios.js
中
// lib/axios.js
axios.CancelToken = require('./cancel/CancelToken');
接下来我们看看CancelToken
类做了什么
CancelToken类
1. 构造器
// cancel/CancelToken.js
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) {
// 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.js
var request = new XMLHttpRequest();
// ...
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
// ...
});
当调用cancel function
的时候,会终止掉请求,这就是cancel的实现原理
2. CancelToken.source
// cancel/CancelToken.js
CancelToken.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.js
CancelToken.prototype.throwIfRequested = function throwIfRequested() {
if (this.reason) {
throw this.reason;
}
};
当取消时,抛错
在Axios.prototype.request
的dispatchRequest
中
// core/dispathRequest.js
function 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.js
token.reason = new Cancel(message);
token.reason
是Cancel
的实例,它表示了cancelToken的状态。而验证状态是由接下来这个方法实现的。
isCancel方法
module.exports = function isCancel(value) {
return !!(value && value.__CANCEL__);
};
xhrAdapter
浏览器端的请求适配器
// adapters/xhr.js
module.exports = function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest() {})
})
接收配置config
作为参数,返回一个promise
dispatchXhrRequest
就是ajax的封装,来看看他是怎么封装的
- 首先,最基础的一个请求建立,发送流程
// adapters/xhr.js
var 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() {}
// ...
// 设置headers
if ('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.js
request.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.js
if (typeof config.onDownloadProgress === 'function') {
request.addEventListener('progress', config.onDownloadProgress);
}
if (typeof config.onUploadProgress === 'function' && request.upload) {
request.upload.addEventListener('progress', config.onUploadProgress);
}
// ...
- 取消请求,之前已经提及过
// adapters/xhr.js
if (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十分的灵活,更易扩展,其实现方式也值得学习使用。