JavaScript前端异步操作超时处理:Axios与Fetch的完整解决方案
本文详细解析JavaScript前端异步操作的超时处理机制。针对Axios,介绍通过配置timeout属性实现基础超时控制;针对Fetch,详解利用AbortController和signal机制中断请求。此外,还提供封装通用超时函数的方法,并解决Promise.race竞态条件下的定时器清理问题,确保异步操作的健壮性与资源释放。
随着 ECMAScript 的 Promise(ES2015)和 async/await(ES2017)特性的普及,异步编程在前端开发中已变得极为普遍。异步代码与同步代码在执行顺序和处理逻辑上存在显著差异,编写异步代码需要开发者具备与同步编程不同的思维模式。
如果一段代码长时间无法执行完毕,会发生什么?
若是同步代码,用户通常会看到界面“无响应”,通俗地说就是程序“卡死”;而对于异步代码,虽然结果可能迟迟未到,但其他代码仍在继续执行,仿佛该操作从未发生一样。
当然,事情并非真的没有发生,只是在不同场景下表现各异。例如,带有加载动画的页面会一直显示加载状态;或者本该更新数据的页面,用户看不到任何数据变化。
再比如某个对话框无法关闭……这些现象我们通常统称为 BUG。但有时,某些异步操作过程并没有明显的“回显”,它默默地“死”在那里,无人知晓,直到页面刷新,连一点痕迹都不会留下。
Axios 自带超时处理
使用 Axios 进行 Web API 调用是一种常见的异步操作。通常我们的代码会这样写:
try {
const res = await axios.get(url, options);
// TODO 正常进行后续业务
} catch(err) {
// TODO 进行容错处理,或者报错
}
这段代码在大多数情况下都能正常运行,直到有一天用户抱怨:“怎么等了半天没反应?”
此时开发者意识到,由于服务器压力增大,该请求已难以瞬时响应。为了提升用户体验,代码中增加了一个 loading 动画:
try {
showLoading();
const res = await axios.get(url, options);
// TODO 正常业务
} catch (err) {
// TODO 容错处理
} finally {
hideLoading();
}
然而有一天,有用户反馈:“我等了半个小时,居然一直在那转圈圈!”于是开发者意识到,由于某种原因,请求被卡死。这种情况下应该重发请求或直接告知用户——因此,需要增加超时检查机制。
幸运的是,Axios 确实支持超时处理,只需在 options 中添加一个 timeout: 3000 即可解决问题。如果发生超时,可以在 catch 块中检测并处理:
try {...}
catch (err) {
if (err.isAxiosError && !err.response && err.request
&& err.message.startsWith("timeout")) {
// 如果是 Axios 的 request 错误,并且消息是延时消息
// TODO 处理超时
}
}
finally {...}
Axios 的问题解决了,那如果使用 fetch() 呢?
处理 fetch() 超时
fetch() 本身不具备处理超时的能力,我们需要判断超时后通过 AbortController 来触发“取消”请求的操作。
如果需要中断一个 fetch() 操作,只需从一个 AbortController 对象获取 signal,并将这个信号对象作为 fetch() 的选项传入。大概就是这样:
const ac = new AbortController();
const { signal } = ac;
fetch(url, { signal }).then(res => {
// TODO 处理业务
});
// 1 秒后取消 fetch 操作
setTimeout(() => ac.abort(), 1000);
ac.abort() 会向 signal 发送信号,触发它的 abort 事件,并将其 .aborted 属性置为 true。fetch() 内部处理会利用这些信息中止掉请求。
上面的示例演示了如何实现 fetch() 操作的超时处理。如果使用 await 的形式来处理,需要把 setTimeout(...) 放在 fetch(...) 之前:
const ac = new AbortController();
const { signal } = ac;
setTimeout(() => ac.abort(), 1000);
const res = await fetch(url, { signal }).catch(() => undefined);
为了避免使用 try ... catch ... 来处理请求失败,这里在 fetch() 后加了一个 .catch(...) 以忽略错误。如果发生错误,res 会被赋值为 undefined。实际的业务处理可能需要更合理的 catch() 处理来让 res 包含可识别的错误信息。
本来到这里就可以结束了,但是对每一个 fetch() 调用都写这么长一段代码,会显得很繁琐,不如封装一下:
async function fetchWithTimeout(timeout, resoure, init = {}) {
const ac = new AbortController();
const signal = ac.signal;
setTimeout(() => ac.abort(), timeout);
return fetch(resoure, { ...init, signal });
}
没问题了吗?不,有问题。
如果我们在上述代码的 setTimeout(...) 里输出一条信息:
setTimeout(() => {
console.log("It's timeout");
ac.abort();
}, timeout);
并且在调用时给予足够的时间:
fetchWithTimeout(5000, url).then(res => console.log("success"));
我们会看到输出 success,并在 5 秒后看到输出 It's timeout。
对了,我们虽然为 fetch(...) 处理了超时,但是并没有在 fetch(...) 成功的情况下干掉 timer。作为一个思维缜密的程序员,怎么能够犯这样的错误呢?干掉他!
async function fetchWithTimeout(timeout, resoure, init = {}) {
const ac = new AbortController();
const signal = ac.signal;
const timer = setTimeout(() => {
console.log("It's timeout");
return ac.abort();
}, timeout);
try {
return await fetch(resoure, { ...init, signal });
} finally {
clearTimeout(timer);
}
}
完美!但问题还没结束。
万物皆可超时
Axios 和 fetch 都提供了中断异步操作的途径,但对于一个不具备 abort 能力的普通 Promise 来说,该怎么办?
对于这样的 Promise,我只能说,让他去吧,随便他去干到天荒地老 —— 反正我也没办法阻止。但生活总得继续,我不能一直等啊!
这种情况下我们可以把 setTimeout() 封装成一个 Promise,然后使用 Promise.race() 来实现“过时不候”:
race 是竞速的意思,所以 Promise.race() 的行为是不是很好理解?
function waitWithTimeout(promise, timeout, timeoutMessage = "timeout") {
let timer;
const timeoutPromise = new Promise((_, reject) => {
timer = setTimeout(() => reject(timeoutMessage), timeout);
});
return Promise.race([timeoutPromise, promise])
.finally(() => clearTimeout(timer)); // 别忘了清 timer
}
可以写一个 Timeout 来模拟看看效果:
(async () => {
const business = new Promise(resolve => setTimeout(resolve, 1000 * 10));
try {
await waitWithTimeout(business, 1000);
console.log("[Success]");
} catch (err) {
console.log("[Error]", err); // [Error] timeout
}
})();
以上就是JavaScript前端超时异步操作完美解决的详细内容,更多关于解决前端超时的异步操作的资料请关注本站其它相关文章!
JavaScript闭包与类(Class)的区别及适用场景对比
2026-11-05 15:48:57
JavaScript入门要学ES6吗?let与箭头函数等新特性解析
2026-11-05 14:49:16
JavaScript 8种数据类型详解:基本类型与引用类型的区别
2026-11-05 14:25:15
怎么在JavaScript中使用async/await优化Ajax异步流程
2026-11-05 13:56:15
JavaScript replace回调函数实现动态正则替换逻辑
2026-11-05 13:52:13
