前端里的“异步”经常被放在一起讨论:Web Workers、AJAX、Fetch、Axios,甚至还有请求取消。但它们解决的其实不是同一个问题。我的理解是先看主线程为什么会卡,再看网络请求怎么发,最后处理多个请求返回顺序不确定带来的竞态。
一、异步不等于多线程
浏览器中的 JavaScript 主线程要执行代码,也要处理页面渲染和用户交互。网络请求等待响应时,不会一直占用主线程,所以 XHR、Fetch 和 Axios 都能异步获取数据;但请求完成后的回调仍然会回到 JavaScript 线程执行。
如果真正耗时的是大数组计算、图片像素处理或复杂数据转换,仅仅改成 Promise 或 async/await 并不能把计算移出主线程。这时才需要 Web Workers。
二、Web Workers 解决计算阻塞
Web Worker 会在独立线程中运行脚本,主线程与 Worker 通过 postMessage 传递数据。它适合 CPU 密集型任务,但不能直接操作 DOM;数据传递通常还会产生结构化克隆成本,因此小任务没必要为了“异步”都放进 Worker。
// main.js
const worker = new Worker('./worker.js');
worker.postMessage(largeData);
worker.onmessage = ({ data }) => {
renderResult(data);
};
// worker.js
self.onmessage = ({ data }) => {
const result = heavyCompute(data);
self.postMessage(result);
};
Worker 中也可以使用 Fetch,但这不代表“发请求就要开 Worker”。浏览器本来就会异步处理网络 I/O,Worker 的主要价值仍然是避免密集计算阻塞界面。
三、从 XMLHttpRequest 到 Fetch
传统原生请求主要使用 XMLHttpRequest。它能监听状态、上传下载进度,也能通过 abort() 取消请求,但事件式 API 比较繁琐。
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
console.log(JSON.parse(xhr.responseText));
}
};
xhr.onerror = () => console.error('网络错误');
xhr.send();
jQuery AJAX 在 XHR 上提供了更方便的封装,统一了早期浏览器差异,也提供 $.ajax、$.get 和 jqXHR.abort()。维护依赖 jQuery 的老项目时它仍然常见,但新项目通常没有必要只为请求功能引入整套 jQuery。
Fetch 是现代浏览器原生 API,基于 Promise,配合 async/await 更自然。不过它有一个容易忽略的差异:服务器返回 404 或 500 时,Fetch 通常不会自动进入 catch,需要主动检查 response.ok。
async function getUsers(signal) {
const response = await fetch('/api/users', { signal });
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
四、Axios 解决项目级请求管理
Axios 是独立的第三方 HTTP 客户端,不应简单理解成“Fetch 的高级封装”。它在浏览器中长期使用 XHR 适配请求,但对外提供 Promise API,并内置 JSON 转换、超时配置、请求与响应拦截器等能力。
如果项目需要统一添加 Token、处理 401、转换业务响应结构和收敛错误提示,Axios 的实例与拦截器比较省事。小型页面或依赖较少的项目,原生 Fetch 加一层轻量封装也完全够用。选择重点不是“谁更新”,而是项目是否需要集中管理请求策略。
五、用 AbortController 防止请求竞态
搜索联想是最典型的竞态场景:用户先输入 a,再输入 ab。第二个请求可能先返回,随后较慢的第一个请求又覆盖页面,界面最终展示了过期数据。
AbortController 是浏览器原生 API。每次发起新请求前取消上一次请求,可以避免旧响应继续更新当前界面。Fetch 原生接收 signal,Axios 0.22 及以上也支持同一套方式。
let controller;
async function search(keyword) {
controller?.abort();
controller = new AbortController();
try {
const response = await fetch(
`/api/search?q=${encodeURIComponent(keyword)}`,
{ signal: controller.signal }
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
renderList(await response.json());
} catch (error) {
if (error.name !== 'AbortError') {
showError(error);
}
}
}
取消请求主要是让客户端停止等待和处理结果,不代表服务端一定停止了已经开始的业务。因此涉及提交、支付等写操作时,后端仍要通过幂等键、版本号或状态校验保证正确性。前端也可以配合请求序号,只允许最新一次结果更新页面。
六、实际项目里怎么选
- 计算任务让页面卡顿:考虑 Web Workers。
- 维护老项目:理解 XHR 和 jQuery AJAX,按现有技术栈处理。
- 现代轻量项目:优先 Fetch,并封装状态检查和错误处理。
- 需要统一拦截、超时和请求配置:使用 Axios。
- 搜索、筛选和路由切换产生过期请求:使用 AbortController,并让最新结果拥有更新权。
把这些能力放在一起看,边界会更清楚:Web Workers 管计算线程,Fetch 和 Axios 管网络通信,AbortController 管请求生命周期。先判断问题属于 CPU 阻塞、请求封装还是结果竞态,再选工具,比背 API 名称更实用。