JavaScript前端超時(shí)異步操作完美解決過(guò)程
自從 ECMAScript 的 Promise?ES2015?和 async/await?ES2017?特性發(fā)布以后,異步在前端界已經(jīng)成為特別常見(jiàn)的操作。異步代碼和同步代碼在處理問(wèn)題順序上會(huì)存在一些差別,編寫(xiě)異步代碼需要擁有跟編寫(xiě)同步代碼不同的“意識(shí)”。
如果一段代碼久久不能執(zhí)行完成,會(huì)怎么樣?
如果這是同步代碼,我們會(huì)看到一種叫做“無(wú)響應(yīng)”的現(xiàn)象,或者通俗地說(shuō) —— “死掉了”;但是如果是一段異步代碼呢?可能我們等不到結(jié)果,但別的代碼仍在繼續(xù),就好像這件事情沒(méi)有發(fā)生一般。
當(dāng)然事情并不是真的沒(méi)發(fā)生,只不過(guò)在不同的情況下會(huì)產(chǎn)生不同的現(xiàn)象。比如有加載動(dòng)畫(huà)的頁(yè)面,看起來(lái)就是一直在加載;又比如應(yīng)該進(jìn)行數(shù)據(jù)更新的頁(yè)面,看不到數(shù)據(jù)變化;
再比如一個(gè)對(duì)話(huà)框,怎么也關(guān)不掉 …… 這些現(xiàn)象我們統(tǒng)稱(chēng)為 BUG。但也有一些時(shí)候,某個(gè)異步操作過(guò)程并沒(méi)有“回顯”,它就默默地死在那里,沒(méi)有人知道,待頁(yè)面刷新之后,就連一點(diǎn)遺跡都不會(huì)留下。
Axios 自帶超時(shí)處理
使用 Axios 進(jìn)行 Web Api 調(diào)用就是一種常見(jiàn)的異步操作過(guò)程。通常我們的代碼會(huì)這樣寫(xiě):
try { const res = await axios.get(url, options); // TODO 正常進(jìn)行后續(xù)業(yè)務(wù) } catch(err) { // TODO 進(jìn)行容錯(cuò)處理,或者報(bào)錯(cuò) }
這段代碼一般情況下都執(zhí)行良好,直到有一天用戶(hù)抱怨說(shuō):怎么等了半天沒(méi)反應(yīng)?
然后開(kāi)發(fā)者意識(shí)到,由于服務(wù)器壓力增大,這個(gè)請(qǐng)求已經(jīng)很難瞬時(shí)響應(yīng)了??紤]到用戶(hù)的感受,加了一個(gè) loading 動(dòng)畫(huà):
try { showLoading(); const res = await axios.get(url, options); // TODO 正常業(yè)務(wù) } catch (err) { // TODO 容錯(cuò)處理 } finally { hideLoading(); }
然而有一天,有用戶(hù)說(shuō):“我等了半個(gè)小時(shí),居然一直在那轉(zhuǎn)圈圈!”于是開(kāi)發(fā)者意識(shí)到,由于某種原因,請(qǐng)求被卡死了,這種情況下應(yīng)該重發(fā)請(qǐng)求,或者直接報(bào)告給用戶(hù) —— 嗯,得加個(gè)超時(shí)檢查。
幸運(yùn)的是 Axios 確實(shí)可以處理超時(shí),只需要在?options
?里添加一個(gè)?timeout: 3000
?就能解決問(wèn)題。如果超時(shí),可以在?catch
?塊中檢測(cè)并處理:
try {...} catch (err) { if (err.isAxiosError && !err.response && err.request && err.message.startsWith("timeout")) { // 如果是 Axios 的 request 錯(cuò)誤,并且消息是延時(shí)消息 // TODO 處理超時(shí) } } finally {...}
Axios 沒(méi)問(wèn)題了,如果用?fetch()
?呢?
處理 fetch() 超時(shí)
fetch()
?自己不具備處理超時(shí)的能力,需要我們判斷超時(shí)后通過(guò)?AbortController
?來(lái)觸發(fā)“取消”請(qǐng)求操作。
如果需要中斷一個(gè)?fetch()
?操作,只需從一個(gè)?AbortController
?對(duì)象獲取?signal
,并將這個(gè)信號(hào)對(duì)象作為?fetch()
?的選項(xiàng)傳入。大概就是這樣:
const ac = new AbortController(); const { signal } = ac; fetch(url, { signal }).then(res => { // TODO 處理業(yè)務(wù) }); // 1 秒后取消 fetch 操作 setTimeout(() => ac.abort(), 1000);
ac.abort()
?會(huì)向?signal
?發(fā)送信號(hào),觸發(fā)它的?abort
?事件,并將其?.aborted
?屬性置為?true
。fetch()
?內(nèi)部處理會(huì)利用這些信息中止掉請(qǐng)求。
上面這個(gè)示例演示了如何實(shí)現(xiàn)?fetch()
?操作的超時(shí)處理。如果使用?await
?的形式來(lái)處理,需要把?setTimeout(...)
?放在?fetch(...)
?之前:
const ac = new AbortController(); const { signal } = ac; setTimeout(() => ac.abort(), 1000); const res = await fetch(url, { signal }).catch(() => undefined);
為了避免使用?try ... catch ...
?來(lái)處理請(qǐng)求失敗,這里在?fetch()
?后加了一個(gè)?.catch(...)
?在忽略錯(cuò)誤的情況。如果發(fā)生錯(cuò)誤,res
?會(huì)被賦值為?undefined
。實(shí)際的業(yè)務(wù)處理可能需要更合理的?catch()
?處理來(lái)讓?res
?包含可識(shí)別的錯(cuò)誤信息。
本來(lái)到這里就可以結(jié)束了,但是對(duì)每一個(gè)?fetch()
?調(diào)用都寫(xiě)這么長(zhǎng)一段代碼,會(huì)顯得很繁瑣,不如封裝一下:
async function fetchWithTimeout(timeout, resoure, init = {}) { const ac = new AbortController(); const signal = ac.signal; setTimeout(() => ac.abort(), timeout); return fetch(resoure, { ...init, signal }); }
沒(méi)問(wèn)題了嗎?不,有問(wèn)題。
如果我們?cè)谏鲜龃a的?setTimeout(...)
?里輸出一條信息:
setTimeout(() => { console.log("It's timeout"); ac.abort(); }, timeout);
并且在調(diào)用的給一個(gè)足夠的時(shí)間:
fetchWithTimeout(5000, url).then(res => console.log("success"));
我們會(huì)看到輸出?success
,并在 5 秒后看到輸出?It's timeout
。
對(duì)了,我們雖然為?fetch(...)
?處理了超時(shí),但是并沒(méi)有在?fetch(...)
?成功的情況下干掉?timer
。作為一個(gè)思維縝密的程序員,怎么能夠犯這樣的錯(cuò)誤呢?干掉他!
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); } }
完美!但問(wèn)題還沒(méi)結(jié)束。
萬(wàn)物皆可超時(shí)
Axios 和 fetch 都提供了中斷異步操作的途徑,但對(duì)于一個(gè)不具備?abort
?能力的普通 Promise 來(lái)說(shuō),該怎么辦?
對(duì)于這樣的 Promise,我只能說(shuō),讓他去吧,隨便他去干到天荒地老 —— 反正我也沒(méi)辦法阻止。但生活總得繼續(xù),我不能一直等?。?/p>
這種情況下我們可以把?setTimeout()
?封裝成一個(gè) Promise,然后使用?Promise.race()
?來(lái)實(shí)現(xiàn)“過(guò)時(shí)不候”:
race 是競(jìng)速的意思,所以?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 }
可以寫(xiě)一個(gè) Timeout 來(lái)模擬看看效果:
(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前端超時(shí)異步操作完美解決的詳細(xì)內(nèi)容,更多關(guān)于解決前端超時(shí)的異步操作的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用JavaScript將Excel轉(zhuǎn)換為JSON示例代碼
這篇文章主要給大家介紹了關(guān)于利用JavaScript將Excel轉(zhuǎn)換為JSON的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用JavaScript具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06小程序?qū)崿F(xiàn)短信登錄倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了小程序?qū)崿F(xiàn)短信登錄倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07JavaScript字符串_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
JavaScript中的字符串就是用''或""括起來(lái)的字符表示。下面通過(guò)本文給大家介紹JavaScript字符串的相關(guān)知識(shí),感興趣的朋友一起看看吧2017-06-06利用window.name實(shí)現(xiàn)windowStorage代碼分享
本文主要介紹了利用window.name實(shí)現(xiàn)windowStorage的功能分享,大家參考使用吧2014-01-01js實(shí)現(xiàn)人民幣大寫(xiě)金額形式轉(zhuǎn)換
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)人民幣大寫(xiě)金額形式轉(zhuǎn)換的相關(guān)資料,需要的朋友可以參考下2016-04-04JavaScript涉及二進(jìn)制的轉(zhuǎn)換方式
這篇文章主要介紹了JavaScript涉及二進(jìn)制的轉(zhuǎn)換方式,具有很好的 參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06