请求处理
前端请求的发展经历了
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;
});
}