news 2026/4/16 14:11:05

从零构建一个轻量级axios替代方案:揭秘XHR封装与拦截器设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零构建一个轻量级axios替代方案:揭秘XHR封装与拦截器设计

从零构建一个轻量级axios替代方案:揭秘XHR封装与拦截器设计

1. 为什么需要自建HTTP请求库?

在当今前端开发中,HTTP请求库就像空气一样无处不在。axios作为目前最流行的选择,确实提供了强大的功能和良好的开发体验。但你是否想过,当面试官问你"axios是如何工作的"时,除了说"它很好用",还能给出什么深度回答?

构建自己的HTTP请求库有几个实际价值:

  • 深入理解底层机制:通过手写实现,你会真正明白XMLHttpRequest(XHR)的工作原理
  • 定制化需求:商业项目可能有特殊需求,如独特的认证流程或日志记录
  • 性能优化:去除不需要的功能,打造更轻量的解决方案
  • 面试加分:展示对底层原理的理解,而不只是API调用

我曾在一个性能敏感的项目中,发现axios的体积对首屏加载造成了压力。通过自建精简版请求库,最终减少了约40%的包体积。

2. 基础XHR封装:打造核心请求功能

2.1 XMLHttpRequest基础

现代浏览器都内置了XMLHttpRequest对象,它是所有AJAX请求的基石。让我们先看一个最简单的GET请求示例:

const xhr = new XMLHttpRequest(); xhr.open('GET', 'https://api.example.com/data', true); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { console.log(JSON.parse(xhr.responseText)); } else { console.error('Request failed'); } } }; xhr.send();

这段代码虽然简单,但已经包含了HTTP请求的核心要素:请求方法、URL、异步处理和响应处理。

2.2 Promise封装

现代前端开发中,Promise已经成为异步处理的标准。让我们将上面的代码用Promise包装:

function request(config) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open(config.method || 'GET', config.url, true); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { resolve({ data: JSON.parse(xhr.responseText), status: xhr.status, statusText: xhr.statusText }); } else { reject(new Error(`Request failed with status ${xhr.status}`)); } } }; xhr.send(config.data ? JSON.stringify(config.data) : null); }); }

现在我们可以这样使用:

request({ url: 'https://api.example.com/data', method: 'GET' }).then(response => { console.log(response.data); }).catch(error => { console.error(error); });

2.3 支持多种请求方法

我们的库应该支持常见的HTTP方法:GET、POST、PUT、DELETE等。关键区别在于:

  • GET/DELETE:参数通过URL传递
  • POST/PUT:参数通过请求体传递
function request(config) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); let url = config.url; const method = (config.method || 'GET').toUpperCase(); // 处理GET/DELETE参数 if (method === 'GET' || method === 'DELETE') { if (config.params) { const params = new URLSearchParams(); Object.keys(config.params).forEach(key => { params.append(key, config.params[key]); }); url += `?${params.toString()}`; } } xhr.open(method, url, true); // 设置POST/PUT请求头 if (method === 'POST' || method === 'PUT') { xhr.setRequestHeader('Content-Type', 'application/json'); } xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status >= 200 && xhr.status < 300) { resolve({ data: JSON.parse(xhr.responseText), status: xhr.status, statusText: xhr.statusText }); } else { reject(new Error(`Request failed with status ${xhr.status}`)); } } }; xhr.send(method === 'POST' || method === 'PUT' ? JSON.stringify(config.data) : null); }); }

3. 拦截器系统设计

拦截器是axios最强大的功能之一,它允许我们在请求发出前和响应返回后插入处理逻辑。典型的应用场景包括:

  • 统一添加认证头
  • 请求/响应数据转换
  • 错误统一处理
  • 日志记录

3.1 拦截器基本结构

拦截器本质上是一个中间件系统,包含请求拦截器和响应拦截器。我们可以用数组来存储这些拦截器:

class MiniAxios { constructor() { this.interceptors = { request: [], response: [] }; } useRequestInterceptor(fulfilled, rejected) { this.interceptors.request.push({ fulfilled, rejected }); } useResponseInterceptor(fulfilled, rejected) { this.interceptors.response.push({ fulfilled, rejected }); } }

3.2 实现拦截器链

拦截器的执行顺序是:请求拦截器 → 发送请求 → 响应拦截器。我们可以用Promise链来实现:

class MiniAxios { // ...之前的代码 request(config) { // 初始配置 let promise = Promise.resolve(config); // 请求拦截器 this.interceptors.request.forEach(interceptor => { promise = promise.then(interceptor.fulfilled, interceptor.rejected); }); // 发送请求 promise = promise.then(this.dispatchRequest); // 响应拦截器 this.interceptors.response.forEach(interceptor => { promise = promise.then(interceptor.fulfilled, interceptor.rejected); }); return promise; } dispatchRequest(config) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); // ...XHR实现代码 }); } }

3.3 拦截器实战应用

让我们看几个实际应用场景:

添加认证头:

const api = new MiniAxios(); api.useRequestInterceptor(config => { config.headers = config.headers || {}; config.headers.Authorization = `Bearer ${localStorage.getItem('token')}`; return config; });

统一错误处理:

api.useResponseInterceptor( response => response, error => { if (error.response && error.response.status === 401) { // 跳转到登录页 window.location.href = '/login'; } return Promise.reject(error); } );

4. 取消请求实现

取消请求是一个常被忽视但非常重要的功能。想象用户快速切换标签页时,取消前一个未完成的请求可以显著提升性能。

4.1 基于CancelToken的实现

axios的取消机制基于CancelToken。我们可以实现类似的逻辑:

class CancelToken { constructor(executor) { let cancel; this.promise = new Promise(resolve => { cancel = resolve; }); executor(message => { cancel(message); }); } } class MiniAxios { // ...之前的代码 request(config) { if (config.cancelToken) { config.cancelToken.promise.then(message => { // 这里应该取消XHR请求 if (this.xhr) { this.xhr.abort(); reject(new Cancel(message)); } }); } // ...其他代码 } }

使用示例:

const source = new CancelToken(c => cancel = c); api.get('/data', { cancelToken: source }).catch(err => { if (err instanceof Cancel) { console.log('Request canceled', err.message); } }); // 取消请求 cancel('Operation canceled by user');

4.2 实际应用场景

避免重复请求:

let currentRequest = null; function fetchData() { if (currentRequest) { currentRequest.cancel('New request started'); } currentRequest = new CancelToken(c => cancel = c); api.get('/data', { cancelToken: currentRequest }).then(response => { currentRequest = null; // 处理响应 }); }

组件卸载时取消请求:

useEffect(() => { const source = new CancelToken(c => cancel = c); api.get('/data', { cancelToken: source }) .then(response => { // 更新状态 }); return () => { cancel('Component unmounted'); }; }, []);

5. 与json-server集成测试

为了测试我们的库,可以使用json-server快速搭建一个模拟API服务。

5.1 设置json-server

首先安装并创建测试数据:

npm install -g json-server

创建db.json

{ "posts": [ { "id": 1, "title": "json-server", "author": "typicode" } ], "comments": [ { "id": 1, "body": "some comment", "postId": 1 } ] }

启动服务:

json-server --watch db.json

5.2 测试我们的请求库

const api = new MiniAxios(); // 测试GET api.request({ url: 'http://localhost:3000/posts' }).then(response => { console.log('GET response:', response.data); }); // 测试POST api.request({ url: 'http://localhost:3000/posts', method: 'POST', data: { title: 'New Post', author: 'Me' } }).then(response => { console.log('POST response:', response.data); }); // 测试拦截器 api.useRequestInterceptor(config => { console.log('Request sent to:', config.url); return config; }); api.useResponseInterceptor(response => { console.log('Response received:', response.status); return response; });

6. 性能优化与扩展

6.1 请求缓存

对于不常变化的数据,可以添加缓存功能:

class MiniAxios { constructor() { this.cache = new Map(); } request(config) { const cacheKey = JSON.stringify(config); if (config.cache && this.cache.has(cacheKey)) { return Promise.resolve(this.cache.get(cacheKey)); } return this.dispatchRequest(config).then(response => { if (config.cache) { this.cache.set(cacheKey, response); } return response; }); } }

6.2 并发控制

限制同时进行的请求数量:

class MiniAxios { constructor(maxRequests = 5) { this.maxRequests = maxRequests; this.queue = []; this.activeRequests = 0; } request(config) { return new Promise((resolve, reject) => { this.queue.push({ config, resolve, reject }); this.processQueue(); }); } processQueue() { if (this.activeRequests < this.maxRequests && this.queue.length) { const { config, resolve, reject } = this.queue.shift(); this.activeRequests++; this.dispatchRequest(config) .then(resolve) .catch(reject) .finally(() => { this.activeRequests--; this.processQueue(); }); } } }

6.3 超时处理

添加请求超时功能:

dispatchRequest(config) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); let isTimeout = false; const timeoutTimer = setTimeout(() => { isTimeout = true; xhr.abort(); reject(new Error(`Timeout of ${config.timeout}ms exceeded`)); }, config.timeout || 10000); xhr.onreadystatechange = () => { if (isTimeout) return; // ...正常处理 }; // ...其他XHR设置 }); }

7. 完整实现与对比

现在我们已经实现了axios的核心功能。让我们对比一下自建方案与axios:

功能自建方案axios
基本请求
拦截器
取消请求
自动转换JSON
浏览器/Node仅浏览器都支持
体积~3KB~10KB
测试覆盖率需自测完善
文档和社区支持丰富

在实际项目中,如果需求简单且对体积敏感,自建方案是个不错的选择。但对于复杂项目,axios仍然是更稳妥的选择。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 10:27:20

探索Voron 2.4:从开源设计到专业3D打印的实践指南

探索Voron 2.4&#xff1a;从开源设计到专业3D打印的实践指南 【免费下载链接】Voron-2 项目地址: https://gitcode.com/gh_mirrors/vo/Voron-2 Voron 2.4作为开源社区协作的杰出成果&#xff0c;展现了桌面级3D打印技术的卓越水平。这款高度模块化的高速打印机不仅具备…

作者头像 李华
网站建设 2026/4/15 20:43:54

GPEN多尺寸输入适配策略:从小图到大图的修复逻辑

GPEN多尺寸输入适配策略&#xff1a;从小图到大图的修复逻辑 1. 什么是GPEN&#xff1f;不只是“放大”&#xff0c;而是“重建”人脸 你有没有试过翻出十年前的自拍照&#xff0c;发现连眼睛都糊成一片&#xff1f;或者用AI画图工具生成人像时&#xff0c;总在五官细节上栽跟…

作者头像 李华
网站建设 2026/4/14 2:12:41

Context Engineering与Prompt Engineering实战指南:从原理到最佳实践

Context Engineering与Prompt Engineering实战指南&#xff1a;从原理到最佳实践 摘要&#xff1a;本文针对开发者在构建AI应用时面临的上下文管理混乱和提示词效果不稳定问题&#xff0c;深入解析Context Engineering与Prompt Engineering的核心原理。通过对比不同技术方案&am…

作者头像 李华
网站建设 2026/4/15 12:33:10

Z-Image-Turbo文生图实战,输入即出图

Z-Image-Turbo文生图实战&#xff0c;输入即出图 你有没有过这样的体验&#xff1a;在ComfyUI里敲完提示词&#xff0c;按下生成键&#xff0c;然后盯着进度条数到第5秒——心里已经开始怀疑是不是卡住了&#xff1f;再等3秒&#xff0c;终于出图&#xff0c;结果发现手写的“…

作者头像 李华
网站建设 2026/4/15 14:02:09

OrCAD信号完整性分析实战:S参数导入与仿真

以下是对您提供的博文《OrCAD信号完整性分析实战:S参数导入与仿真技术深度解析》的 全面润色与专业重构版本 。本次优化严格遵循您的全部要求: ✅ 彻底去除AI痕迹,语言自然、老练、有工程师现场感 ✅ 摒弃“引言/概述/总结”等模板化结构,以真实工程逻辑驱动行文节奏 …

作者头像 李华
网站建设 2026/4/16 11:05:07

麦橘超然Flux生成失败怎么办?常见问题全解

麦橘超然Flux生成失败怎么办&#xff1f;常见问题全解 1. 为什么你的麦橘超然Flux总是“卡在生成”或“报错退出”&#xff1f; 你输入了精心设计的提示词&#xff0c;点击“开始生成图像”&#xff0c;界面却迟迟没反应——或者弹出一串红色报错信息&#xff0c;最终只留下一个…

作者头像 李华