JavaScript前端超时异步操作完美解决过程
⽬录
如果⼀段代码久久不能执⾏完成,会怎么样?
Axios ⾃带超时处理
处理 fetch() 超时
万物皆可超时
⾃从 ECMAScript 的 Promi ES2015 和 async/await ES2017 特性发布以后,异步在前端界已经成为特别常见的操作。异步代码和同步代码在处理问题顺序上会存在⼀些差别,编写异步代码需要拥有跟编写同步代码不同的“意识”。
如果⼀段代码久久不能执⾏完成,会怎么样?
如果这是同步代码,我们会看到⼀种叫做“⽆响应”的现象,或者通俗地说 —— “死掉了”;但是如果是⼀段异步代码呢?可能我们等不到结果,但别的代码仍在继续,就好像这件事情没有发⽣⼀般。
当然事情并不是真的没发⽣,只不过在不同的情况下会产⽣不同的现象。⽐如有加载动画的页⾯,看起来就是⼀直在加载;⼜⽐如应该进⾏数据更新的页⾯,看不到数据变化;
再⽐如⼀个对话框,怎么也关不掉 …… 这些现象我们统称为 BUG。但也有⼀些时候,某个异步操作过程并没有“回显”,它就默默地死在那⾥,没有⼈知道,待页⾯刷新之后,就连⼀点遗迹都不会留下。
Axios ⾃带超时处理weirdo
使⽤ Axios 进⾏ Web Api 调⽤就是⼀种常见的异步操作过程。通常我们的代码会这样写:
try {
杭外剑桥const res = (url, options);
// TODO 正常进⾏后续业务
} catch(err) {
// TODO 进⾏容错处理,或者报错
}
这段代码⼀般情况下都执⾏良好,直到有⼀天⽤户抱怨说:怎么等了半天没反应?
然后开发者意识到,由于服务器压⼒增⼤,这个请求已经很难瞬时响应了。考虑到⽤户的感受,加了⼀个 loading 动画:
try {
showLoading();
const res = (url, options);
// TODO 正常业务
} catch (err) {
// TODO 容错处理
} finally {
hideLoading();
}
互动英文然⽽有⼀天,有⽤户说:“我等了半个⼩时,居然⼀直在那转圈圈!”于是开发者意识到,由于某种原因,请求被卡死了,这种情况下应该重发请求,或者直接报告给⽤户 —— 嗯,得加个超时检查。
幸运的是 Axios 确实可以处理超时,只需要在options⾥添加⼀个timeout: 3000就能解决问题。如果超时,可以在catch块中检测并处理:
try {...}ceremony
catch (err) {
if (err.isAxiosError && !spon && questisadora
&& ssage.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 => {backorder
// TODO 处理业务
});
// 1 秒后取消 fetch 操作
tTimeout(() => ac.abort(), 1000);
ac.abort()会向signal发送信号,触发它的abort事件,并将其.aborted属性置为true。fetch()内部处理会利⽤这些信息中⽌掉请求。
上⾯这个⽰例演⽰了如何实现fetch()操作的超时处理。如果使⽤await的形式来处理,需要把tTimeout(...)放在fetch(...)之前:
const ac = new AbortController();
const { signal } = ac;
tTimeout(() => 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;
tTimeout(() => ac.abort(), timeout);
return fetch(resoure, { ...init, signal });
}
没问题了吗?不,有问题。
如果我们在上述代码的tTimeout(...)⾥输出⼀条信息:
tTimeout(() => {
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 = tTimeout(() => {
console.log("It's timeout");
return ac.abort();
}, timeout);
try {
return await fetch(resoure, { ...init, signal });
} finally {
firetrap
clearTimeout(timer);
}
玩具总动员3下载}sos的意思
完美!但问题还没结束。
万物皆可超时
Axios 和 fetch 都提供了中断异步操作的途径,但对于⼀个不具备abort能⼒的普通 Promi 来说,该怎么办?
对于这样的 Promi,我只能说,让他去吧,随便他去⼲到天荒地⽼ —— 反正我也没办法阻⽌。但⽣活总得继续,我不能⼀直等啊!
这种情况下我们可以把tTimeout()封装成⼀个 Promi,然后使⽤Promi.race()来实现“过时不候”:
race 是竞速的意思,所以Promi.race()的⾏为是不是很好理解?
function waitWithTimeout(promi, timeout, timeoutMessage = "timeout") {
let timer;
const timeoutPromi = new Promi((_, reject) => {
timer = tTimeout(() => reject(timeoutMessage), timeout);
});
return Promi.race([timeoutPromi, promi])
treaty
.finally(() => clearTimeout(timer)); // 别忘了清 timer
}
可以写⼀个 Timeout 来模拟看看效果:
(async () => {
const business = new Promi(resolve => tTimeout(resolve, 1000 * 10));
try {
await waitWithTimeout(business, 1000);
console.log("[Success]");
} catch (err) {
console.log("[Error]", err); // [Error] timeout
}
})();
以上就是JavaScript前端超时异步操作完美解决的详细内容,更多关于解决前端超时的异步操作的资料请关注其它相关⽂章!