axios 源码解析:十分钟带你实现一个 mini-axios
本文将带你在十分钟内快速了解并实现一个精简版的 axios
,整个流程分为以下 5 个大部分:
- 准备测试环境
- axios 核心请求构建
- 多宿主环境(浏览器 || node)适配思想
- 拦截器的实现原理
- 如何取消请求
1. 准备基础的测试环境
1.1 基于 Koa 准备一个最简单的服务程序:
我们使用 Koa 搭建一个基础的服务端环境,用于测试浏览器端的请求。
import Koa from 'koa';
const app = new Koa();
// 一个简单的路由处理函数
app.use(async ctx => {
ctx.body = 'Hello, World!';
});
// 启动服务器
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
为支持跨域请求,还需要添加支持跨域的中间件:
app.use(async (ctx, next) => {
ctx.set('Access-Control-Allow-Origin', '*'); // 允许所有来源
ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
ctx.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (ctx.request.method === 'OPTIONS') {
ctx.status = 200;
return;
}
await next();
});
1.2 准备浏览器和 Node.js 端测试环境:
在浏览器端测试环境中,我们可以准备一个简单的 HTML 文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./index.js"></script>
</body>
</html>
基础的 Node.js 测试环境相对简单,只需确保代码不包含浏览器相关的 API,Node.js 也可以运行。
2. axios 核心请求构建
2.1 axios 入口模块
首先,我们创建一个 axios.js
文件,开发 Axios
类,并初始化 axios
工厂函数,最后导出 axios
实例。
// util.js
export const mergeConfig = (config1, config2) => {
return Object.assign(config1, config2);
};
// axios.js
import { mergeConfig } from './utils.js';
class Axios {
constructor(defaultConfig) {
this.defaultConfig = defaultConfig;
}
requiest(url, options) {
try {
this._requiest(url, options);
} catch (error) {
console.error(error);
}
}
_requiest(url, options) {
console.log('开始发送请求', url, options);
}
}
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig);
const instance = Axios.prototype.requiest.bind(context);
instance.create = function (instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig));
};
return instance;
}
const axios = createInstance({
timeout: 0,
adapter: ["xhr", "http", "fetch"],
beseURL: "",
headers: {}
});
axios.Axios = Axios;
axios.default = axios;
export default axios;
现在 axios
的基础功能已经实现,可以在测试文件中导入并进行测试。
import axios from './axios.js';
axios('http://localhost:3000/');
2.2 参数归一化
参数归一化是一种常见的技术,统一函数的参数格式以简化逻辑。
requestHelper(url, options) {
let config = {};
if (typeof url === 'string') {
config = options || {};
config.url = url;
} else if (typeof url === 'object') {
config = url;
}
return config;
}
_requiest(url, options) {
const config = mergeConfig(this.defaultConfig, this.requestHelper(url, options));
console.log('最终的配置', config);
}
3. 多环境请求发送
axios
需要兼容不同环境,因此提出了适配器的概念,根据当前环境选择不同的请求 API,如 xhr
、fetch
或 Node.js 的 http
模块。
3.1 实现适配器核心逻辑
我们首先创建一个 Adapte.js
文件,负责适配不同的请求模块:
export default {
getAdapter(adapters) {
adapters = Array.isArray(adapters) ? adapters : [adapters];
let handler = null;
for (const adapter of adapters) {
handler = adapteConfig[adapter];
if (handler) break;
}
return handler;
}
};
然后,在 dispatchRequest
模块中统一处理请求:
export default function dispatchRequest(config) {
console.log('开始请求发送', config);
}
在 axios.js
中调用该模块:
import dispatchRequest from './dispatchRequest.js';
_requiest(url, options) {
const config = mergeConfig(this.defaultConfig, this.requestHelper(url, options));
dispatchRequest(config);
}
4. axios 拦截器实现
拦截器允许开发者在请求或响应前后进行操作。axios
实现拦截器的方式是通过一个队列,依次执行任务。
4.1 实现拦截器逻辑
我们定义一个 InterceptorManager
类,用于管理拦截器:
class InterceptorManager {
constructor() {
this.handlers = [];
}
use(fulfilled, rejected) {
this.handlers.push({ fulfilled, rejected });
return this.handlers.length - 1;
}
}
export default InterceptorManager;
在 Axios
类中导入并初始化拦截器:
import InterceptorManager from './InterceptorManager.js';
class Axios {
constructor(defaultConfig) {
this.defaultConfig = defaultConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager(),
};
}
}
在请求时处理拦截器:
_requiest(url, options) {
const config = mergeConfig(this.defaultConfig, this.requestHelper(url, options));
const chain = [dispatchRequest, undefined];
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fulfilled, interceptor.rejected);
});
this.interceptors.response.handlers.forEach(interceptor => {
chain.push(interceptor.fulfilled, interceptor.rejected);
});
let promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
}
5. axios 取消请求的实现
5.1 CancelToken 实现
CancelToken
通过 Promise 来管理取消请求事件。
class CancelToken {
constructor(executor) {
let resolvePromise;
this.promise = new Promise(resolve => {
resolvePromise = resolve;
});
executor(message => {
this.reason = new CanceledError(message);
resolvePromise(this.reason);
});
}
static source() {
let cancel;
const token = new CancelToken(c => {
cancel = c;
});
return { token, cancel };
}
}
总结
本文简要介绍了如何使用 5 个步骤实现一个 mini-axios
,涵盖了基础请求构建、多环境适配、拦截器实现以及取消请求功能。通过这个实现,我们对 axios
的内部原理有了更深入的理解,能够帮助我们在项目中更加灵活地使用它。