<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          處理可能超時的異步操作

          共 4203字,需瀏覽 9分鐘

           ·

          2021-11-20 09:44

          作者:邊城

          簡介:從事軟件開發(fā) 20 年,在軟件分析、設(shè)計、架構(gòu)、開發(fā)及軟件開發(fā)技術(shù)研究和培訓(xùn)等方面有著非常豐富的經(jīng)驗,近年主要在研究 Web 前端技術(shù)、基于 .NET 的后端開發(fā)技術(shù)和相關(guān)軟件架構(gòu)。

          來源:SegmentFault ?思否社區(qū)


          自從 ECMAScript 的 Promise?ES2015?和 async/await?ES2017?特性發(fā)布以后,異步在前端界已經(jīng)成為特別常見的操作。異步代碼和同步代碼在處理問題順序上會存在一些差別,編寫異步代碼需要擁有跟編寫同步代碼不同的“意識”,為此我還專門寫了一篇「異步編程需要“意識”」,不過看的人不多,可能確實“無趣”。

          本文要聊的問題可能仍然“無趣”,但很現(xiàn)實 —— 如果一段代碼久久不能執(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?屬性置為?truefetch()?內(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
          ????}
          })();

          至于如何寫可以中止的異步操作,下次再聊。

          -?END -


          瀏覽 38
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品无码内射 | 微信群假人 | 三级国产在线 | 国产又黄又粗又硬 | 翔田千里無碼破解 |