JavaScript原生Ajax(XMLHttpRequest)不支持拦截器,但可通过封装fetch函数模拟请求/响应拦截:请求前修改配置(如加token)、响应后统一处理状态码、JSON解析及错误分类;XHR仅能通过重写open/send方法做简单监听与副作用操作,难以实现异步中断或链式处理。

JavaScript 原生 Ajax(XMLHttpRequest)本身不支持拦截器机制,但可以通过封装请求函数、重写原型方法或使用现代 fetch 配合代理/包装来模拟请求/响应拦截逻辑。下面分两种主流方式说明:基于 fetch 的拦截实现(推荐),以及对 XMLHttpRequest 的有限拦截方案。
用 fetch 封装实现请求与响应拦截器
fetch 是 Promise-based 的 API,天然适合链式拦截。只需封装一个自定义的 request 函数,在发起前处理配置(请求拦截),在 Promise resolve/reject 后处理响应(响应拦截):
-
请求拦截:修改
url、添加 headers(如 token)、序列化 body、日志记录等 - 响应拦截:统一解析 JSON、检查状态码(如 401 跳登录)、错误格式标准化、埋点上报等
示例代码:
const axiosLike = {
interceptors: {
request: [],
response: []
},
useRequestInterceptor(fn) {
this.interceptors.request.push(fn);
},
useResponseInterceptor(onFulfilled, onRejected) {
this.interceptors.response.push({ onFulfilled, onRejected });
},
async request(config) {
// 请求拦截链
let reqConfig = { ...config };
for (const interceptor of this.interceptors.request) {
reqConfig = await interceptor(reqConfig) || reqConfig;
}
try {
const res = await fetch(reqConfig.url, {
method: reqConfig.method || 'GET',
headers: { ...reqConfig.headers },
body: reqConfig.body
});
// 响应拦截链
for (const interceptor of this.interceptors.response) {
if (interceptor.onFulfilled) {
await interceptor.onFulfilled(res);
}
}
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
}
const data = await res.json();
return data;
} catch (err) {
for (const interceptor of this.interceptors.response) {
if (interceptor.onRejected) {
await interceptor.onRejected(err);
}
}
throw err;
}
}
};
// 使用示例
axiosLike.useRequestInterceptor(config => {
config.headers = {
...config.headers,
'Authorization': 'Bearer ' + localStorage.getItem('token')
};
console.log('✅ 请求已添加 token');
return config;
});
axiosLike.useResponseInterceptor(
async (res) => {
console.log('✅ 收到响应,状态:', res.status);
},
(err) => {
console.error('❌ 请求失败:', err.message);
if (err.message.includes('401')) {
location.href = '/login';
}
}
);
// 发起请求
axiosLike.request({ url: '/api/user' });
对 XMLHttpRequest 的“拦截”(仅限监听与简单改写)
XMLHttpRequest 是命令式 API,无法像 fetch 那样自然拦截,但可通过重写其 open 和 send 方法,或监听 onload/onerror 实现轻量级钩子:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 重写
open可记录 URL、方法;重写send可注入 headers 或 body - 无法真正“中断”或“替换”请求,只能在发出前后做副作用(如打点、加 loading)
- 不推荐用于复杂拦截逻辑(比如 token 过期刷新),因难以控制异步流程
简易 patch 示例:
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(...args) {
this._url = args[1];
this._method = args[0];
return originalOpen.apply(this, args);
};
XMLHttpRequest.prototype.send = function(body) {
// 请求拦截:自动加 token
if (this._url && !this._headersSent) {
this.setRequestHeader('Authorization', 'Bearer ' + localStorage.getItem('token'));
this._headersSent = true;
}
return originalSend.apply(this, arguments);
};
// 响应拦截(通过 onload/onerror)
const originalOnLoad = XMLHttpRequest.prototype.onload;
XMLHttpRequest.prototype.onload = function() {
console.log('✅ XHR 响应完成,状态:', this.status);
// 这里可做统一错误处理,但无法直接 throw 或修改返回值
if (originalOnLoad) originalOnLoad.call(this);
};
为什么不用原生 Ajax 做完整拦截?
根本原因在于 XMLHttpRequest 的设计是事件驱动、无返回值、不可链式组合:
- 没有返回 Promise,无法用
then/catch统一处理响应 - 无法在
send前“暂停”并等待异步操作(如刷新 token)完成 - 拦截逻辑分散在多个事件回调中,难以维护和复用
因此,生产环境建议优先使用 fetch 封装,或直接采用成熟库(如 Axios),它已在内部完善实现了拦截器、取消请求、超时、适配器等能力。
小结:该选哪种?
若项目已用 fetch,推荐自行封装带拦截的请求函数——轻量、可控、符合现代 JS 习惯;若必须兼容老浏览器且坚持用 XHR,只做简单日志或 header 注入即可,复杂逻辑请交由上层业务处理。拦截器本质是关注点分离,核心不在“怎么截”,而在“截完做什么”。

















