前言
究竟对 axios 了解多少?这是我这阵一直想要问的问题!之前一直认为这只是一个工具,只要会用就没问题,但是当被一个问题问住的时候,我觉得应该多了解它了,毕竟这是一个公认的,开源的,牛 X 的第三方工具。
Axios 为什么能区分是浏览器请求还是服务器请求
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== "undefined") {
// For browsers use XHR adapter
adapter = require("../adapters/xhr");
} else if (
typeof process !== "undefined" &&
Object.prototype.toString.call(process) === "[object process]"
) {
// For node use HTTP adapter
adapter = require("../adapters/http");
}
return adapter;
}
Axios 内部封装了一个适配器,判断是否有 XMLHttpRequest,如果存在则调用 xhr,如果不是则判断 process 是否存在,是否为 Node 环境。
可以查看 getDefaultAdapter 函数用在了哪里
var defaults = {
transitional: transitionalDefaults,
adapter: getDefaultAdapter(),
...
}
module.exports = defaults;
作为默认值输出。
查看 defaults.adapter 用在哪里
**
* 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
*/
module.exports = function dispatchRequest(config) {
...
var adapter = config.adapter || defaults.adapter;
return adapter(config).then(function onAdapterResolution(response) {
throwIfCancellationRequested(config);
// Transform response data
response.data = transformData.call(
config,
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.call(
config,
reason.response.data,
reason.response.headers,
config.transformResponse
);
}
}
return Promise.reject(reason);
});
};
查看 dispatchRequest
/**
* Dispatch a request
*
* @param {Object} config The config specific for this request (merged with this.defaults)
*/
Axios.prototype.request = function request(configOrUrl, config) {
/*eslint no-param-reassign:0*/
// Allow for axios('example/url'[, config]) a la fetch API
if (typeof configOrUrl === 'string') {
config = config || {};
config.url = configOrUrl;
} else {
config = configOrUrl || {};
}
config = mergeConfig(this.defaults, config);
// Set config.method
if (config.method) {
config.method = config.method.toLowerCase();
} else if (this.defaults.method) {
config.method = this.defaults.method.toLowerCase();
} else {
config.method = 'get';
}
...
var newConfig = config;
...
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
...
return promise;
};
可以看出最后 getDefaultAdapter 用在了 Axios 的 request 方法上,即请求时,就会调用这个适配器,判断请求环境。并且可以看出 request 方法接收两个参数,一个是 配置项或者 url,另一个是配置项,如果第一个参数是字符串就当作 url 传入,如果是其他类型就和默认配置项合并成一个新的对象。
Axios 为什么能中断请求
官网给出取消请求的方式:
- 可以使用 CancelToken.source 工厂方法创建 cancel token,像这样:
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,
}
);
// 取消请求(message 参数是可选的)
source.cancel("Operation canceled by the user.");
- 还可以通过传递一个 executor 函数到 CancelToken 的构造函数来创建 cancel token:
const CancelToken = axios.CancelToken;
let cancel;
axios.get("/user/12345", {
cancelToken: new CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
}),
});
// cancel the request
cancel();
注意: 可以使用同一个 cancel token 取消多个请求
- 从 v0.22.0 Axios 支持 AbortController 取消请求获取 API 的方式:
const controller = new AbortController();
axios
.get("/foo/bar", {
signal: controller.signal,
})
.then(function (response) {
//...
});
// cancel the request
controller.abort();
在 github 上可以看到,最新的 readme 可以看到,前两种形式即 CancelToken 方式已经不太推荐。主要原因是可以使用 web 原生自带的 AbortController 类。
在源码中两个位置查看这块取消
- 查看 CancelToken 这个类
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;
// eslint-disable-next-line func-names
this.promise.then(function (cancel) {
if (!token._listeners) return;
var i;
var l = token._listeners.length;
for (i = 0; i < l; i++) {
token._listeners[i](cancel);
}
token._listeners = null;
});
// eslint-disable-next-line func-names
this.promise.then = function (onfulfilled) {
var _resolve;
// eslint-disable-next-line func-names
var promise = new Promise(function (resolve) {
token.subscribe(resolve);
_resolve = resolve;
}).then(onfulfilled);
promise.cancel = function reject() {
token.unsubscribe(_resolve);
};
return promise;
};
executor(function cancel(message) {
if (token.reason) {
// Cancellation has already been requested
return;
}
token.reason = new CanceledError(message);
resolvePromise(token.reason);
});
}
- 关键代码,在 xhr.js 文件中
if (config.cancelToken || config.signal) {
// Handle cancellation
// eslint-disable-next-line func-names
onCanceled = function (cancel) {
if (!request) {
return;
}
reject(!cancel || (cancel && cancel.type) ? new CanceledError() : cancel);
request.abort();
request = null;
};
config.cancelToken && config.cancelToken.subscribe(onCanceled);
if (config.signal) {
config.signal.aborted
? onCanceled()
: config.signal.addEventListener("abort", onCanceled);
}
}
从源码上可以看出,取消请求主要是调用 XMLHttpRequest 的 abort 方法,当使用 signal 方法进行取消时,判断请求是否已经取消。
Axios 是如何实现请求拦截和响应拦截的
官网给出拦截器的用法
在请求或响应被 then 或 catch 处理前拦截它们。
// 添加请求拦截器
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);
}
);
如果你想在稍后移除拦截器,可以这样:
const myInterceptor = axios.interceptors.request.use(function () {
/*...*/
});
axios.interceptors.request.eject(myInterceptor);
可以为自定义 axios 实例添加拦截器
const instance = axios.create();
instance.interceptors.request.use(function () {
/*...*/
});
拦截器的作用是,在请求前后实现一些公共功能,如在请求前实现一些检验,在请求后实现接口统一的错误拦截。
在源码中查看如何注册拦截器
在 Axios.js 中可以看到下列代码:
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
我们更深层次挖掘一下,查看 InterceptorManager 类:
function InterceptorManager() {
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
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null,
});
return this.handlers.length - 1;
};
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
/**
* Iterate over all the registered interceptors
*
* This method is particularly useful for skipping over any
* interceptors that may have become `null` calling `eject`.
*
* @param {Function} fn The function to call for each interceptor
*/
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};
注册拦截器,当使用 use 方法时,将拦截器按 fulfilled, rejected, options 封装成一个对象,按顺序 push 在各自 InterceptorManager 的 handlers 中。
如何执行拦截器
- 当设置请求拦截器为同步时
if (!synchronousRequestInterceptors) {
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain);
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
- 执行请求拦截器
while (requestInterceptorChain.length) {
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break;
}
}
- 执行响应拦截器
while (responseInterceptorChain.length) {
promise = promise.then(
responseInterceptorChain.shift(),
responseInterceptorChain.shift()
);
}
从源码中可以看出来,只要相应的数组长度不为 0,就会一直执行下去,直到最后一个相应拦截器执行完毕,返回的依旧是一个 promise。
注意:请求响应拦截器一定要有返回值,不然结束后无法获取最终的结果
总结
- 通过判断是否有 XMLHttpRequest 和 process 判断当前请求是处在浏览器环境还是 node 环境。
- 通过 XMLHttpRequest 对象的 abort 方法取消请求操作,在 v0.22.0 之后可以用 AbortController 来取消。
- 拦截器通过 InterceptorManager 的 use 方法进行保存在 handlers 中,通过 请求拦截器 -> 分发http请求 -> 响应拦截器 顺序执行。