超时处理实战经验分享:前端开发中的那些坑与解决方案
项目初期的技术选型
最近我们团队接了一个新项目,是个电商网站。本来以为就是个常规的前端活儿,没想到一上来就遇到了不少问题。其中一个比较头疼的问题就是请求超时处理。
在做这个项目之前,我一直觉得超时处理没啥大不了的,毕竟不就是加个timeout嘛。结果真正开始做了才发现,这事儿还真没那么简单。
开始没想到:超时处理的复杂性
刚开始的时候,我简单地在每个请求里加了个timeout配置,以为这样就能搞定一切。结果上线后用户反馈一堆,说有时候请求半天没反应,页面卡得不行。这才意识到,超时处理不仅仅是设置一个时间那么简单。
我开始上网查资料,发现了不少关于超时处理的最佳实践。然后就开始尝试各种方案,希望能找到一个靠谱的解决方案。
核心代码就这几行
经过一番折腾,最后我决定采用Promise.race的方式来处理超时。这种方式可以很好地控制请求的超时时间,并且代码也比较简洁。
这里是我最终实现的代码:
const fetchWithTimeout = (url, options, timeout = 5000) => {
const controller = new AbortController();
const { signal } = controller;
const timeoutId = setTimeout(() => {
controller.abort();
console.error('请求超时');
}, timeout);
return fetch(url, { ...options, signal })
.then(response => {
clearTimeout(timeoutId);
return response;
})
.catch(error => {
if (error.name === 'AbortError') {
console.error('请求被取消');
}
throw error;
});
};
// 使用示例
fetchWithTimeout('https://jztheme.com/api/data', { method: 'GET' })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
这段代码的核心就是在fetch请求中使用AbortController来控制请求的取消。通过Promise.race的方式,我们可以设置一个定时器,如果请求在设定的时间内没有完成,就会触发abort事件,从而取消请求。
最大的坑:性能问题
虽然上面的代码看起来挺不错的,但实际运行起来还是有些问题。首先是性能问题,我发现每次请求都会创建一个新的AbortController实例,感觉有点浪费资源。后来调整了方案,改成全局的AbortController实例,但这又引入了新的问题,比如不同请求之间的干扰。
为了解决这个问题,我引入了一个Map来存储每个请求的AbortController实例,确保每个请求都有独立的控制。改完后的代码如下:
const abortControllers = new Map();
const fetchWithTimeout = (url, options, timeout = 5000) => {
const controller = new AbortController();
const { signal } = controller;
const key = ${url}-${JSON.stringify(options)};
abortControllers.set(key, controller);
const timeoutId = setTimeout(() => {
controller.abort();
console.error('请求超时');
abortControllers.delete(key);
}, timeout);
return fetch(url, { ...options, signal })
.then(response => {
clearTimeout(timeoutId);
abortControllers.delete(key);
return response;
})
.catch(error => {
if (error.name === 'AbortError') {
console.error('请求被取消');
}
abortControllers.delete(key);
throw error;
});
};
// 使用示例
fetchWithTimeout('https://jztheme.com/api/data', { method: 'GET' })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
这样,每个请求都有自己独立的AbortController实例,并且在请求完成后会从Map中删除对应的实例,避免了内存泄漏的问题。
回顾与反思
经过这一番折腾,总算把超时处理的问题解决了。虽然还有一些小问题没有完全解决(比如某些极端情况下请求还是会卡住),但总体来说已经达到了预期的效果。
通过这次经历,我深刻体会到超时处理的重要性。不仅仅是为了提升用户体验,更是为了保证系统的稳定性和可靠性。希望我的这些经验对你也有帮助,如果你有更好的解决方案,欢迎在评论区交流。
