JavaScript前端超時異步操作完美解決過程
自從 ECMAScript 的 Promise?ES2015?和 async/await?ES2017?特性發(fā)布以后,異步在前端界已經(jīng)成為特別常見的操作。異步代碼和同步代碼在處理問題順序上會存在一些差別,編寫異步代碼需要擁有跟編寫同步代碼不同的“意識”。
如果一段代碼久久不能執(zhí)行完成,會怎么樣?
如果這是同步代碼,我們會看到一種叫做“無響應(yīng)”的現(xiàn)象,或者通俗地說 —— “死掉了”;但是如果是一段異步代碼呢?可能我們等不到結(jié)果,但別的代碼仍在繼續(xù),就好像這件事情沒有發(fā)生一般。
當然事情并不是真的沒發(fā)生,只不過在不同的情況下會產(chǎn)生不同的現(xiàn)象。比如有加載動畫的頁面,看起來就是一直在加載;又比如應(yīng)該進行數(shù)據(jù)更新的頁面,看不到數(shù)據(jù)變化;
再比如一個對話框,怎么也關(guān)不掉 …… 這些現(xiàn)象我們統(tǒng)稱為 BUG。但也有一些時候,某個異步操作過程并沒有“回顯”,它就默默地死在那里,沒有人知道,待頁面刷新之后,就連一點遺跡都不會留下。
Axios 自帶超時處理
使用 Axios 進行 Web Api 調(diào)用就是一種常見的異步操作過程。通常我們的代碼會這樣寫:
try {
const res = await axios.get(url, options);
// TODO 正常進行后續(xù)業(yè)務(wù)
} catch(err) {
// TODO 進行容錯處理,或者報錯
}
這段代碼一般情況下都執(zhí)行良好,直到有一天用戶抱怨說:怎么等了半天沒反應(yīng)?
然后開發(fā)者意識到,由于服務(wù)器壓力增大,這個請求已經(jīng)很難瞬時響應(yīng)了??紤]到用戶的感受,加了一個 loading 動畫:
try {
showLoading();
const res = await axios.get(url, options);
// TODO 正常業(yè)務(wù)
} catch (err) {
// TODO 容錯處理
} finally {
hideLoading();
}
然而有一天,有用戶說:“我等了半個小時,居然一直在那轉(zhuǎn)圈圈!”于是開發(fā)者意識到,由于某種原因,請求被卡死了,這種情況下應(yīng)該重發(fā)請求,或者直接報告給用戶 —— 嗯,得加個超時檢查。
幸運的是 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?來觸發(fā)“取消”請求操作。
如果需要中斷一個?fetch()?操作,只需從一個?AbortController?對象獲取?signal,并將這個信號對象作為?fetch()?的選項傳入。大概就是這樣:
const ac = new AbortController();
const { signal } = ac;
fetch(url, { signal }).then(res => {
// TODO 處理業(yè)務(wù)
});
// 1 秒后取消 fetch 操作
setTimeout(() => ac.abort(), 1000);
ac.abort()?會向?signal?發(fā)送信號,觸發(fā)它的?abort?事件,并將其?.aborted?屬性置為?true。fetch()?內(nèi)部處理會利用這些信息中止掉請求。
上面這個示例演示了如何實現(xiàn)?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(...)?在忽略錯誤的情況。如果發(fā)生錯誤,res?會被賦值為?undefined。實際的業(yè)務(wù)處理可能需要更合理的?catch()?處理來讓?res?包含可識別的錯誤信息。
本來到這里就可以結(jié)束了,但是對每一個?fetch()?調(diào)用都寫這么長一段代碼,會顯得很繁瑣,不如封裝一下:
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);
并且在調(diào)用的給一個足夠的時間:
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);
}
}
完美!但問題還沒結(jié)束。
萬物皆可超時
Axios 和 fetch 都提供了中斷異步操作的途徑,但對于一個不具備?abort?能力的普通 Promise 來說,該怎么辦?
對于這樣的 Promise,我只能說,讓他去吧,隨便他去干到天荒地老 —— 反正我也沒辦法阻止。但生活總得繼續(xù),我不能一直等啊!
這種情況下我們可以把?setTimeout()?封裝成一個 Promise,然后使用?Promise.race()?來實現(xiàn)“過時不候”:
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前端超時異步操作完美解決的詳細內(nèi)容,更多關(guān)于解決前端超時的異步操作的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用JavaScript將Excel轉(zhuǎn)換為JSON示例代碼
這篇文章主要給大家介紹了關(guān)于利用JavaScript將Excel轉(zhuǎn)換為JSON的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用JavaScript具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-06-06
JavaScript字符串_動力節(jié)點Java學院整理
JavaScript中的字符串就是用''或""括起來的字符表示。下面通過本文給大家介紹JavaScript字符串的相關(guān)知識,感興趣的朋友一起看看吧2017-06-06
利用window.name實現(xiàn)windowStorage代碼分享
本文主要介紹了利用window.name實現(xiàn)windowStorage的功能分享,大家參考使用吧2014-01-01

