axios源码阅读分为三个部分。
1. 文件目录篇
2. 应用篇
3. 手写一个axios
阅读本篇文章之前建议先阅读我的上一篇axios源码阅读三部曲之文件目录篇
axios请求数据方式
四种方案
// 方案一 axios(options);
axios({
url,
method,
headers,
});
axios.request({
url,
method,
headers,
});
// 方案二 axios(url, options);
axios(url, {
method,
headers,
});
axios.request(url, {
method,
headers,
});
// 方案三——对于get、delete等方法:axios.get(url, options);
axios.get(url, {
headers,
});
// 方案四——对于post、put等方法:axios.post(url, data, options);
axios.post(url, data, {
headers,
});
源码分析
对于方案一、方案二可以看到有两种不同的写法,直接使用axios或通过axios.request访问。
/**
* 创建一个axios实例
* @param {Object} defaultConfig 默认配置
* @returns {Axios} 一个新的Axios实例
*/
function createInstance(defaultConfig) {
// 创建一个axios上下文
const context = new Axios(defaultConfig);
// 将axios实例的request对象绑定axios上下文
const instance = bind(Axios.prototype.request, context);
// 将axios实例拷贝到instance并绑定axios上下文
utils.extend(instance, Axios.prototype, context);
// 将axios上下文拷贝到instance
utils.extend(instance, context);
return instance;
}
// 创建一个默认axios实例,用于导出
const axios = createInstance(defaults);
屏蔽掉两行utils.extend代码后,可以明显的看出,axios指向的是Axios.prototype.request函数。因此我们使用axios(options)和axios.request(options)是一样的。
看到这我们会有这样一个疑问?既然axios指向的是Axios.prototype.request函数。又怎么使用axios.request来调用在Axios原型上的request函数呢?
在这里我们就要看utils.extend这个函数了,utils.extend函数主要是扩展对象属性。
/**
* 通过拷贝copy对象的属性来扩展extended对象的属性
*
* @param {Object} extended 要扩展的对象
* @param {Object} copy 要复制属性的对象
* @param {Object} thisArg 要绑定功能的对象
* @returns {Object} 扩展后的对象
*/
function extend(extended, copy, thisArg) {
forEach(copy, function assignValue(val, key) {
if (thisArg && isFunction(val)) {
extended[key] = bind(val, thisArg);
} else {
extended[key] = val;
}
});
return extended;
}
通过utils.extend方法成功的将Aixos原型上的所有属性和方法都绑定到了instance上,因此axios也可以调用Axios原型上的方法。
对于方案三和方案四。
/**
* 为支持的请求方法设置别名
*/
utils.forEach(
["delete", "get", "head", "options"],
function forEachMethodNoData(method) {
Axios.prototype[method] = function (url, config = {}) {
return this.request(
utils.merge(config, {
method: method,
url: url,
})
);
};
}
);
utils.forEach(["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,
})
);
};
});
其实质是对request方法的某些请求进行简化封装。
取消请求
使用方式
const source = axios.CancelToken.source();
axios.get('/urlA', {
cancelToken: source.token
});
axios.get('/urlB').then(() => {
source.cancel("B请求成功了");
});
源码分析
/**
* “CancelToken”是一个可用于请求取消操作的对象。
*
* @class
* @param {Function} executor 执行器
*/
function CancelToken(executor) {
if (!utils.isFunction(executor)) {
throw new TypeError("executor必须是一个函数");
}
let resolvePromise;
this.promise = new Promise(function promiseExecutor(resolve) {
resolvePromise = resolve;
});
const token = this;
executor(function cancel(message) {
if (token.reason) {
// 此时已经要求取消
return;
}
token.reason = new Cancel(message);
resolvePromise(token.reason);
});
}
/**
* 返回一个对象,该对象包含一个新的“CancelToken”和一个函数,该函数在调用时将取消“ CancelToken”。
*/
CancelToken.source = function source() {
let cancel;
const token = new CancelToken(function executor(c) {
cancel = c;
});
return {
token: token,
cancel: cancel,
};
};
// xhr.js
if (config.cancelToken) {
// 处理取消
config.cancelToken.promise.then(function onCanceled(cancel) {
if (!request) {
return;
}
request.abort();
reject(cancel);
// 清理请求
request = null;
});
}
取消请求是利用Promise的异步操作完成。
通过souce方法暴露出cancel和cancelToken,用户只需要执行cancel函数将异步状态从pending转换成fulfilled即可。
axios通过cancelToken.promise获取到异步实例。然后在then方法中执行request.abort()来取消请求。
配置全局设置
两种方案
// 方案一 (不推荐)
axios.defaults.baseURL = 'http://...';
axios.defaults.timeout = 3000;
// 方案二
const _axios = axios.create({
baseURL: 'http://...',
timeout: 3000
});
方案一:通过修改axios默认配置实现;方案二:合并新的配置和默认配置项,然后重新创建axios实例。
源码分析
// 工厂方法:创建一个新实例
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
将默认配置defaults和instanceConfig进行合并;配置的优先级是用户配置 > 默认配置。
拦截器
两种拦截器
拦截器分请求拦截器和响应拦截器。
axios.interceptors.request.use(
config => {
return config;
},
error => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
response => {
return response;
},
error => {
return Promise.reject(error);
}
);
源码分析
// Axios.js
/**
* 创建一个axios实例
*
* @class
* @param {Object} instanceConfig axios默认配置
*/
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
// 拦截器核心实现逻辑
const chain = [dispatchRequest, undefined];
const 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());
}
// InterceptorManager.js
/**
* 拦截器管理对象
*
* @class
*/
function InterceptorManager() {
this.handlers = [];
}
/**
*将新的拦截器添加到堆栈中
*
* @param {Function} fulfilled 完成状态处理Promise的“then“函数
* @param {Function} rejected 拒绝状态处理Promise的“catch”函数
*
* @return {Number} 一个ID,用于以后删除拦截器
*/
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected,
});
return this.handlers.length - 1;
}
拦截器管理对象的本质是一个数组队列,而拦截器的本质是Promise的应用。
因此添加拦截器的时候需要两个函数,一个用来处理Promise的fulfilled这个状态,一个用来处理Promise的rejected这个状态。这里处理Promise异常的方式不是用catch,而是利用then的第二个参数。
Promise.prototype.then(onFulfilled, onRejected)
onFulfilled 可选
当 Promise 变成接受状态(fulfilled)时调用的函数。该函数有一个参数,即接受的最终结果(the fulfillment value)。如果该参数不是函数,则会在内部被替换为 (x) => x,即原样返回 promise 最终结果的函数
onRejected 可选
当 Promise 变成拒绝状态(rejected)时调用的函数。该函数有一个参数,即拒绝的原因(rejection reason)。 如果该参数不是函数,则会在内部被替换为一个 "Thrower" 函数 (it throws an error it received as argument)。
因此为了确保拦截器异常可以被catch捕获,我们通常会在拦截器的异常处理函数中返回Promise.reject(error)。