Skip to main content

请求处理

前端请求的发展经历了 XHR -> Fetch -> Axios 等封装库。现代开发中最常被问到的是它们三者的区别、如何取消请求,以及 Fetch 的缺陷与局限性。

方案原理优点缺点/局限
AJAX (XHR)浏览器原生 XMLHttpRequest 对象兼容性极好,支持进度监听 (onprogress),支持取消 (abort)。基于事件的异步模型,写法繁琐,容易陷入回调地狱;且 API 设计老旧,不符合关注分离原则。
Fetch API浏览器原生 ES6 API,基于 Promise语法简洁,符合现代异步编程思想(结合 async/await);脱离了 XHR,是底层的网络请求接口。1. 默认不携带 cookie(需设置 credentials: 'include')。
2. 无法原生监听上传进度。
3. 对 400/500 等 HTTP 错误状态码不会 reject,只有网络断开才会 reject,需手动检查 res.ok
4. 不支持原生设置超时时间(需借助 AbortController)。
Axios第三方库 (基于 Promise)浏览器端基于 XHR,Node 端基于 http 模块。功能极强:支持拦截器、自动转换 JSON、防御 XSRF、并发请求等。需要引入第三方包,增加了一点点体积。

如何取消请求?

AbortController

AbortController 并不是专门为 Fetch 或 Axios 设计的,它是 DOM 原生提供的一个用于中止一个或多个 Web 请求(甚至事件监听器)的通用接口。 它的工作原理是:实例化一个控制器,拿到它身上的 signal 信号对象并传递给任何支持中止的 API。当你调用 controller.abort() 时,signal 对象会触发一个 abort 事件,底层的 API(如 fetch 或 setTimeout 封装)监听到这个信号后就会立即中断操作。

面试官常问:如果用户在搜索框疯狂输入,或者频繁切换 Tab,如何取消上一次还未完成的冗余请求?

Axios 取消请求

Axios 在 v0.22.0 之后,推荐使用现代浏览器的 AbortController API(原先的 CancelToken 已被废弃)。

// 1. 创建控制器
const controller = new AbortController();

// 2. 将 signal 传给 axios
axios
.get("/api/data", {
signal: controller.signal,
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log("请求被取消", thrown.message);
}
});

// 3. 在需要的时候取消请求
controller.abort("用户手动取消");

Fetch 取消请求

Fetch 本身没有 abort 方法,同样需要借助 AbortController

const controller = new AbortController();

fetch("/api/data", {
signal: controller.signal,
})
.then((res) => res.json())
.catch((err) => {
if (err.name === "AbortError") {
console.log("Fetch 请求被取消");
}
});

// 触发取消
controller.abort();

原生 XHR 取消请求

调用实例的 xhr.abort() 方法即可。

Fetch 如何实现超时控制?

因为 Fetch 默认没有 timeout 配置,面试官喜欢让你手写一个带有超时的 Fetch,考察你对 Promise 和底层 API 的理解:

function fetchWithTimeout(url, options = {}, timeout = 5000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), timeout);

return fetch(url, {
...options,
signal: controller.signal,
})
.then((response) => {
clearTimeout(id); // 请求成功,清除定时器
return response;
})
.catch((error) => {
clearTimeout(id);
// 拦截 abort 抛出的错误,将其转化为超时错误
if (error.name === "AbortError") {
throw new Error("请求超时");
}
throw error;
});
}