axios简介
axios是什么?
axios是一个基于Promise的HTTP库,可以用在浏览器和node.js之中使用。
axios的特性
从浏览器中创建 XMLHttpRequests
从 node.js 创建 http 请求
支持 Promise API
拦截请求和响应
转换请求数据和响应数据
取消请求
自动转换 JSON 数据
客户端支持防御 XSRF
axios常用的方法
axios(config):最简单的用法,可以发送任何类型的请求
axios.request(config):用法跟axios(config)相同
axios.get(url[, config]):发送get请求
axios.post(url[, data[, config]]):发送post请求
axios.delete(url[, config]):发送delete请求
axios.put(url[, data[, config]]):发送put请求
axios.defaults.xxx: 请求的默认全局配置
axios.interceptors.request.use(): 添加请求拦截器
axios.interceptors.response.use(): 添加响应拦截器
axios.create([config]): 创建一个新的 axios(它没有下面的功能)
axios.Cancel(): 用于创建取消请求的错误对象
axios.CancelToken(): 用于创建取消请求的 token 对象
axios.isCancel(): 是否是一个取消请求的错误
axios.all(promises): 用于批量执行多个异步请求
axios.spread(): 用来指定接收所有成功数据的回调函数的方法
这里要注意一下请求方式的配置,get、delete和post、put的配置是不一样的
axios主要源码
主要文件目录
├── /dist/ # 项目输出目录
├── /lib/ # 项目源码目录
│ ├── /adapters/ # 定义请求的适配器 xhr、http
│ │ ├── http.js # 实现 http 适配器(包装 http 包)
│ │ └── xhr.js # 实现 xhr 适配器(包装 xhr 对象)
│ ├── /cancel/ # 定义取消功能
│ ├── /core/ # 一些核心功能
│ │ ├── Axios.js # axios 的核心主类
│ │ ├── dispatchRequest.js # 用来调用 http 请求适配器方法发送请求的函数
│ │ ├── InterceptorManager.js # 拦截器的管理器
│ │ └── settle.js # 根据 http 响应状态,改变 Promise 的状态
│ ├── /helpers/ # 一些辅助方法
│ ├── axios.js # 对外暴露接口
│ ├── /defaults/ # axios 的默认配置
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置 TypeScript 的声明文件
└── index.js # 入口文件
入口
根目录下的index.js是入口文件
import axios from './lib/axios.js';
export default axios;
再来到 ./lib/axios.js
从代码可以看出axios其实是Axios的一个实例
import Axios from './core/Axios.js';
...
/**
* Create an instance of Axios
*
* @param {Object} defaultConfig The default config for the instance
*
* @returns {Axios} A new instance of Axios
*/
function createInstance(defaultConfig) {
//使用默认配置创建Axios实例
const context = new Axios(defaultConfig);
/*
bind函数的作用跟Axios.prototype.request.bind类似,将
Axios.prototype.request函数的this显式绑定为context
返回值instance是一个函数,作用跟Axios.prototype.request一样
*/
const instance = bind(Axios.prototype.request, context);
/*
extend的作用是将Axios.prototype的方法和属性扩展到instance上
*/
utils.extend(instance, Axios.prototype, context, {allOwnKeys: true});
//将context的方法和属性扩展到instance上
utils.extend(instance, context, {allOwnKeys: true});
/*
工厂函数,用来返回创建实例对象的函数
createInstance的参数是默认参数+instance的参数
*/
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
// Create the default instance to be exported
const axios = createInstance(defaults);
//下面是直接axios上直接挂载方法
// Expose Axios class to allow class inheritance
axios.Axios = Axios;
// Expose Cancel & CancelToken
axios.CanceledError = CanceledError;
axios.CancelToken = CancelToken;
axios.isCancel = isCancel;
axios.VERSION = VERSION;
axios.toFormData = toFormData;
// Expose all/spread
axios.all = function all(promises) {
return Promise.all(promises);
};
...
这块代码比较重要的一点就是instance是一个函数,当它调用时,即相当于axios({})这样调用,实际上就是Axios.prototype.request在执行
/lib/core/Axios.js
// Axios类,初始化了默认配置,请求和响应两个拦截器
class Axios {
constructor(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
/**
* Dispatch a request
*
* @param {String|Object} configOrUrl The config specific for this request (merged with this.defaults)
* @param {?Object} config
*
* @returns {Promise} The Promise to be fulfilled
*/
request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
/*
如果configOrUrl是字符串,那么它就是url,config就是配置对象 axios(url,[config])
否则configOrUrl就是一个配置对象 axios([config])
*/
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
//将默认配置与用户调用时传入的配置进行合并
config = mergeConfig(this.defaults, config);
const transitional = config.transitional;
if (transitional !== undefined) {
validator.assertOptions(transitional, {
silentJSONParsing: validators.transitional(validators.boolean),
forcedJSONParsing: validators.transitional(validators.boolean),
clarifyTimeoutError: validators.transitional(validators.boolean)
}, false);
}
//设置请求方法,如果config中没有配置方法,就采用默认方法或者设置为get
config.method = (config.method || this.defaults.method || 'get').toLowerCase();
// Flatten headers
const defaultHeaders = config.headers && utils.merge(
config.headers.common,
config.headers[config.method]
);
defaultHeaders && utils.forEach(
['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
function cleanHeaderConfig(method) {
delete config.headers[method];
}
);
//设置请求头
config.headers = new AxiosHeaders(config.headers, defaultHeaders);
// 过滤跳过的拦截器
const requestInterceptorChain = [];
//是否同步执行
let synchronousRequestInterceptors = true;
//此方法对请求拦截器每一项执行函数unshiftRequestInterceptors,把拦截器的每一项存入requestInterceptorChain
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
//如果有一个是异步的,那么整个队列都是异步的
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
//unshift,后定义的拦截器是先执行的
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
//响应拦截器,push是先定义先执行
const responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise;
let i = 0;
let len;
//如果是异步的
if (!synchronousRequestInterceptors) {
//dispatchRequest是发送请求的函数
const chain = [dispatchRequest.bind(this), undefined];
//请求拦截器异步执行完后再执行派发请求,最后再执行响应拦截器
chain.unshift.apply(chain, requestInterceptorChain);
chain.push.apply(chain, responseInterceptorChain);
len = chain.length;
//promise是成功的状态
promise = Promise.resolve(config);
while (i < len) {
//promise是成功状态,所以会执行第一个chain[i++]回调
promise = promise.then(chain[i++], chain[i++]);
}
//返回promise对象
return promise;
}
//同步则顺序执行
len = requestInterceptorChain.length;
let newConfig = config;
i = 0;
while (i < len) {
const onFulfilled = requestInterceptorChain[i++];
const onRejected = requestInterceptorChain[i++];
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected.call(this, error);
break;
}
}
//请求拦截器执行后再执行派发请求
try {
promise = dispatchRequest.call(this, newConfig);
} catch (error) {
return Promise.reject(error);
}
i = 0;
len = responseInterceptorChain.length;
//执行响应拦截器,异步执行
while (i < len) {
promise = promise.then(responseInterceptorChain[i++], responseInterceptorChain[i++]);
}
//返回promise对象
return promise;
}
getUri(config) {
config = mergeConfig(this.defaults, config);
const fullPath = buildFullPath(config.baseURL, config.url);
return buildURL(fullPath, config.params, config.paramsSerializer);
}
}
//下面两个forEach是通过Axios的原型挂载各种请求方法
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
/*eslint func-names:0*/
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method,
url,
data: (config || {}).data
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
/*eslint func-names:0*/
function generateHTTPMethod(isForm) {
return function httpMethod(url, data, config) {
return this.request(mergeConfig(config || {}, {
method,
headers: isForm ? {
'Content-Type': 'multipart/form-data'
} : {},
url,
data
}));
};
}
Axios.prototype[method] = generateHTTPMethod();
Axios.prototype[method + 'Form'] = generateHTTPMethod(true);
});
export default Axios;
从以上源码可知,Axios工作流程如下
- 通过createInstence创建Axios实例axios
- 对axios做一些配置
- 执行请求拦截器(requestInterceptorManager)
- 派发请求(dispatchRequest)
- 执行响应拦截器(responseInterceptorManager)
- 最后返回一个promise
拦截器
前面在看Axios类的时候contructor里就有两个拦截器
import InterceptorManager from './InterceptorManager.js';
class Axios {
constructor(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
...
}
到/lib/core/InterceptorManager.js ,可以看到InterceptorManager的具体内容
import utils from './../utils.js';
class InterceptorManager {
constructor() {
this.handlers = [];
}
/**
* Add a new interceptor to the stack
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
*/
use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled,//成功回调
rejected,//失败回调
synchronous: options ? options.synchronous : false, //同步还是异步执行
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
}
//注销拦截器
eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
}
//清空拦截器
clear() {
if (this.handlers) {
this.handlers = [];
}
}
//这个的作用就是在把拦截器的fullfilled和rejected回调,加入chain(见Axios.js)
forEach(fn) {
//遍历this.handlers的每一项,让其去执行第二个函数
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
}
}
export default InterceptorManager;
可以看出,一个InterceptorManager对象都保存着一个handlers数组,这个数组用来保存一个对象,这个对象里包含了许多拦截函数。
/*
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*
* @param {Boolean} [allOwnKeys = false]
* @returns {void}
*/
function forEach(obj, fn, {allOwnKeys = false} = {}) {
// Don't bother if no value provided
if (obj === null || typeof obj === 'undefined') {
return;
}
let i;
let l;
// Force an array if not already something iterable
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj];
}
//其实就是遍历obj的每一项,让每一项去执行函数fn
if (isArray(obj)) {
// Iterate over array values
for (i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// Iterate over object keys
const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
const len = keys.length;
let key;
for (i = 0; i < len; i++) {
key = keys[i];
fn.call(null, obj[key], key, obj);
}
}
}
派发请求
dispatchRequest
/**
* Dispatch a request to the server using the configured adapter.
*
* @param {object} config The config that is to be used for the request
*
* @returns {Promise} The Promise to be fulfilled
*/
export default function dispatchRequest(config)
//如果被取消的请求被发送出去, 抛出错误
throwIfCancellationRequested(config);
//确保headers存在
config.headers = AxiosHeaders.from(config.headers);
//转换请求数据
config.data = transformData.call(
config,
config.transformRequest
);
//获取适配器,可以自定义适配器
const adapter = config.adapter || defaults.adapter;
//发送请求, 返回请求后 promise 对象 ajax HTTP
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
//转换响应数据
response.data = transformData.call(
config,
config.transformResponse,
response
);
response.headers = AxiosHeaders.from(response.headers);
//设置 promise 成功的值为 响应结果
return response;
}, function onAdapterRejection(reason) {
if (!isCancel(reason)) {
throwIfCancellationRequested(config);
// Transform response data
if (reason && reason.response) {
reason.response.data = transformData.call(
config,
config.transformResponse,
reason.response
);
reason.response.headers = AxiosHeaders.from(reason.response.headers);
}
}
return Promise.reject(reason);
});
}
可以看到派发请求主要就是以下几步:
- 设置请求头、适配器、转换请求数据
- 发送请求
- 转换响应数据、设置响应头
transformData
在派发请求时,转换数据有以下代码:
//转换请求数据
config.data = transformData.call(
config,
config.transformRequest
);
//转换响应数据
response.data = transformData.call(
config,
config.transformResponse,
response
);
- 可以看到转换数据时,是将config作为this去调用transformData函数,config.transformRequest和 config.transformResponse,response这些作为参数传给函数
接着可以继续看transformData的实现
/**
* Transform the data for a request or a response
*
* @param {Array|Function} fns A single function or Array of functions
* @param {?Object} response The response object
*
* @returns {*} The resulting transformed data
*/
export default function transformData(fns, response) {
const config = this || defaults;
const context = response || config;
const headers = AxiosHeaders.from(context.headers);
let data = context.data;
//fns是处理数据的数组,遍历fns,用data、headers、response去作为参数去调用fn获取data
utils.forEach(fns, function transform(fn) {
data = fn.call(config, data, headers.normalize(), response ? response.status : undefined);
});
headers.normalize();
return data;
}
- transformData本身并没有转换数据,它只是遍历处理的函数去处理
- 补充一点,就是fns即 transformRequest、transformResponse是处理数据的函数,可以用户自己补充处理函数的
transformRequest
transformRequest: [function transformRequest(data, headers) {
const contentType = headers.getContentType() || '';
//用户是否设置数据为json格式
const hasJSONContentType = contentType.indexOf('application/json') > -1;
//是否是对象类型
const isObjectPayload = utils.isObject(data);
if (isObjectPayload && utils.isHTMLForm(data)) {
data = new FormData(data);
}
const isFormData = utils.isFormData(data);
if (isFormData) {
if (!hasJSONContentType) {
//如果是FormData数据而且没有设置json格式,那么直接返回FormData格式的数据
return data;
}
//有设置就要转换为json格式
return hasJSONContentType ? JSON.stringify(formDataToJSON(data)) : data;
}
if (utils.isArrayBuffer(data) ||
utils.isBuffer(data) ||
utils.isStream(data) ||
utils.isFile(data) ||
utils.isBlob(data)
) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
headers.setContentType('application/x-www-form-urlencoded;charset=utf-8', false);
return data.toString();
}
//文件类型数据
let isFileList;
if (isObjectPayload) {
if (contentType.indexOf('application/x-www-form-urlencoded') > -1) {
return toURLEncodedForm(data, this.formSerializer).toString();
}
if ((isFileList = utils.isFileList(data)) || contentType.indexOf('multipart/form-data') > -1) {
const _FormData = this.env && this.env.FormData;
return toFormData(
isFileList ? {'files[]': data} : data,
_FormData && new _FormData(),
this.formSerializer
);
}
}
//如果数据是对象类型而且用户规定了json格式,则将数据json化,而且设置Content-Type
if (isObjectPayload || hasJSONContentType ) {
headers.setContentType('application/json', false);
return stringifySafely(data);
}
return data;
}],
- transformRequest其实就是一个数组里面包含一个对数据进行转换的函数
- transformResponse也类似
adapter
适配器是就是真正发送请求的模块了,根据node和浏览器环境区分有两种,node环境用的是http.js,浏览器环境用的是xhr.js
export default function xhrAdapter(config) {
return new Promise(function dispatchXhrRequest(resolve, reject) {
//请求头、请求数据、请求内容
let requestData = config.data;
const requestHeaders = AxiosHeaders.from(config.headers).normalize();
const responseType = config.responseType;
let onCanceled;
...
//创建一个XMLHttpRequest对象
let request = new XMLHttpRequest();
// HTTP basic authentication
if (config.auth) {
const username = config.auth.username || '';
const password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : '';
requestHeaders.set('Authorization', 'Basic ' + btoa(username + ':' + password));
}
//baseURL+url
const fullPath = buildFullPath(config.baseURL, config.url);
//发送请求
request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
//设置超时时间
request.timeout = config.timeout;
//请求结束处理函数
function onloadend() {
if (!request) {
return;
}
// Prepare the response
const responseHeaders = AxiosHeaders.from(
'getAllResponseHeaders' in request && request.getAllResponseHeaders()
);
const responseData = !responseType || responseType === 'text' || responseType === 'json' ?
request.responseText : request.response;
const response = {
data: responseData,
status: request.status,
statusText: request.statusText,
headers: responseHeaders,
config,
request
};
//根据请求结果处理响应结果
settle(function _resolve(value) {
resolve(value);
done();
}, function _reject(err) {
reject(err);
done();
}, response);
// Clean up request
request = null;
}
//onloadend是一个
if ('onloadend' in request) {
// Use onloadend if available
request.onloadend = onloadend;
} else {
// Listen for ready state to emulate onloadend
request.onreadystatechange = function handleLoad() {
if (!request || request.readyState !== 4) {
return;
}
if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) {
return;
}
// readystate handler is calling before onerror or ontimeout handlers,
// so we should call onloadend on the next 'tick'
//setTimeout是宏任务
setTimeout(onloadend);
};
}
// Handle browser request cancellation (as opposed to a manual cancellation)
request.onabort = function handleAbort() {
};
// Handle low level network errors
request.onerror = function handleError() {
};
// Handle timeout
request.ontimeout = function handleTimeout() {
};
// Add xsrf header
// This is only done if running in a standard browser environment.
// Specifically not if we're in a web worker, or react-native.
//添加xsrf头部,预防xsrf攻击
if (utils.isStandardBrowserEnv()) {
// Add xsrf header 如果请求携带cookie或者同源网站就需要添加
const xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath))
&& config.xsrfCookieName && cookies.read(config.xsrfCookieName);
if (xsrfValue) {
requestHeaders.set(config.xsrfHeaderName, xsrfValue);
}
}
// false, 0 (zero number), and '' (empty string) are valid JSON values
if (!requestData && requestData !== false && requestData !== 0 && requestData !== '') {
requestData = null;
}
// Send the request
request.send(requestData);
});
}
其实就是ajax请求promise化
取消请求
用法:
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
cancelToken:new CancelToken(function(c){
cancel = c;
})
}).then(response => {
})
cancel();
在派发请求前就有取消请求
/**
* 这个函数是将要取消的请求取消,会抛出cancel
*/
function throwIfCancellationRequested(config) {
//如果需要取消请求,
if (config.cancelToken) {
config.cancelToken.throwIfRequested();
}
if (config.signal && config.signal.aborted) {
throw new CanceledError();
}
}
export default function dispatchRequest(config) {
throwIfCancellationRequested(config);
config.headers = AxiosHeaders.from(config.headers);
...
}
class CancelToken {
constructor(executor) {
if (typeof executor !== 'function') {
throw new TypeError('executor must be a function.');
}
let resolvePromise;
//往实例对象身上添加promise属性,
this.promise = new Promise(function promiseExecutor(resolve) {
/*
将修改promise对象成功状态的函数暴露出去,就是说resolve()这样调用时,promise状态是成功状态,
现在赋值给resolvePromise,resolvePromise()这样调用,this.promise状态就会是成功状态
*/
resolvePromise = resolve;
});
const token = this;
...
//executor的参数是一个cancel函数,这个函数一执行,resolvePromise就会执行,就会改变this.promise
//的状态
executor(function cancel(message, config, request) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new CanceledError(message, config, request);
resolvePromise(token.reason);
});
}
//用于检查是否需要取消并抛出
throwIfRequested() {
if (this.reason) {
throw this.reason;
}
}
//订阅取消信号
subscribe(listener) {
if (this.reason) {
listener(this.reason);
return;
}
if (this._listeners) {
this._listeners.push(listener);
} else {
this._listeners = [listener];
}
}
//取消订阅信号
unsubscribe(listener) {
if (!this._listeners) {
return;
}
const index = this._listeners.indexOf(listener);
if (index !== -1) {
this._listeners.splice(index, 1);
}
}
//产生CancelToken的工厂方法
static source() {
let cancel;
const token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token,
cancel
};
}
}
- 在上面的使用示例中,构造cancelToken时,参数为executor,executor的参数为c赋值给cancel。在取消请求
时调用cancel(),cancel一执行resolvePromise就会执行,resolvePromise一执行this.promise就是成功的状态
有两个地方会调用 throwIfCancellationRequested函数,在进入派发请求时,如果配置了cancelToken并且token.reason存在,就会抛出,在发送完请求得到响应后,转换数据前也会调用这个函数进行检测。
到底在什么地方取消请求呢?在xhr.js文件中
//如果配置了cancelToken或者signal
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = cancel => {
if (!request) {
return;
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
//取消请求
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);
}
}
- 配置了cancelToken或signal就会取消请求