<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>

          如何優(yōu)雅的捕獲所有異步 error?

          共 6848字,需瀏覽 14分鐘

           ·

          2021-10-26 01:55

          大廠技術(shù)  高級前端  Node進階

          點擊上方 程序員成長指北,關(guān)注公眾號

          回復(fù)1,加入高級Node交流群

          成熟的產(chǎn)品都有較高的穩(wěn)定性要求,僅前端就要做大量監(jiān)控、錯誤上報,后端更是如此,一個未考慮的異常可能導(dǎo)致數(shù)據(jù)錯誤、服務(wù)雪崩、內(nèi)存溢出等等問題,輕則每天焦頭爛額的處理異常,重則引發(fā)線上故障。

          假設(shè)代碼邏輯沒有錯誤,那么剩下的就是異常錯誤了。

          由于任何服務(wù)、代碼都可能存在外部調(diào)用,只要外部調(diào)用存在不確定性,代碼就可能出現(xiàn)異常,所以捕獲異常是一個非常重要的基本功。

          所以本周就精讀 How to avoid uncaught async errors in Javascript 這篇文章,看看 JS 如何捕獲異步異常錯誤。

          概述

          之所以要關(guān)注異步異常,是因為捕獲同步異常非常簡單:

          try {
            ;(() => {
              throw new Error('err')
            }
          )()
          catch (e) {
            console.log(e) // caught
          }

          但異步錯誤卻無法被直接捕獲,這不太直觀:

          try {
            ;(async () => {
              throw new Error('err'// uncaught
            }
          )()
          catch (e) {
            console.log(e)
          }

          原因是異步代碼并不在 try catch 上下文中執(zhí)行,唯一的同步邏輯只有創(chuàng)建一個異步函數(shù),所以異步函數(shù)內(nèi)的錯誤無法被捕獲。

          要捕獲 async 函數(shù)內(nèi)的異常,可以調(diào)用 .catch,因為 async 函數(shù)返回一個 Promise:

          ;(async () => {
            throw new Error('err')
          }
          )().catch((e) => {
            console.log(e// caught
          }
          )

          當(dāng)然也可以在函數(shù)體內(nèi)直接用 try catch

          ;(async () => {
            try {
              throw new Error('err')
            } catch (e) {
              console.log(e// caught
            }
          }
          )()

          類似的,如果在循環(huán)體里捕獲異常,則要使用 Promise.all

          try {
            await Promise.all(
              [123].map(async () => {
                throw new Error('err')
              })
            )
          catch (e) {
            console.log(e) // caught
          }

          也就是說 await 修飾的 Promise 內(nèi)拋出的異常,可以被 try catch 捕獲。

          但不是說寫了 await 就一定能捕獲到異常,一種情況是 Promise 內(nèi)再包含一個異步:

          new Promise(() => {
            setTimeout(() => {
              throw new Error('err'// uncaught
            }, 0)
          }).catch((e) => {
            console.log(e)
          })

          這個情況要用 reject 方式拋出異常才能被捕獲:

          new Promise((res, rej) => {
            setTimeout(() => {
              rej('err'// caught
            }, 0)
          }).catch((e) => {
            console.log(e)
          })

          另一種情況是,這個 await 沒有被執(zhí)行到:

          const wait = (ms) => new Promise((res) => setTimeout(res, ms))

          ;(async () => {
            try {
              const p1 = wait(3000).then(() => {
                throw new Error('err')
              }
          // uncaught
              await wait(2000).then(() => {
                throw new Error('err2')
              }
          // caught
              await p1
            } catch (e) {
              console.log(e)
            }
          }
          )()

          p1 等待 3s 后拋出異常,但因為 2s 后拋出了 err2 異常,中斷了代碼執(zhí)行,所以 await p1 不會被執(zhí)行到,導(dǎo)致這個異常不會被 catch 住。

          而且有意思的是,如果換一個場景,提前執(zhí)行了 p1,等 1s 后再 await p1,那異常就從無法捕獲變成可以捕獲了,這樣瀏覽器會怎么處理?

          const wait = (ms) => new Promise((res) => setTimeout(res, ms))

          ;(async () => {
            try {
              const p1 = wait(1000).then(() => {
                throw new Error('err')
              }
          )
              await wait(2000)
              await p1
            } catch (e) {
              console.log(e)
            }
          }
          )()

          結(jié)論是瀏覽器 1s 后會拋出一個未捕獲異常,但再過 1s 這個未捕獲異常就消失了,變成了捕獲的異常。

          這個行為很奇怪,當(dāng)程序復(fù)雜時很難排查,因為并行的 Promise 建議用 Promise.all 處理:

          await Promise.all([
            wait(1000).then(() => {
              throw new Error('err')
            }), // p1
            wait(2000),
          ])

          另外 Promise 的錯誤會隨著 Promise 鏈傳遞,因此建議把 Promise 內(nèi)多次異步行為改寫為多條鏈的模式,在最后 catch 住錯誤。

          還是之前的例子,Promise 無法捕獲內(nèi)部的異步錯誤:

          new Promise((res, rej) => {
            setTimeout(() => {
              throw Error('err')
            }, 1000// 1
          }).catch((error) => {
            console.log(error)
          })

          但如果寫成 Promise Chain,就可以捕獲了:

          new Promise((res, rej) => {
            setTimeout(res, 1000// 1
          })
            .then((res, rej) => {
              throw Error('err')
            })
            .catch((error) => {
              console.log(error)
            })

          原因是,用 Promise Chain 代替了內(nèi)部多次異步嵌套,這樣多個異步行為會被拆解為對應(yīng) Promise Chain 的同步行為,Promise 就可以捕獲啦。

          最后,DOM 事件監(jiān)聽內(nèi)拋出的錯誤都無法被捕獲:

          document.querySelector('button').addEventListener('click'async () => {
            throw new Error('err'// uncaught
          })

          同步也一樣:

          document.querySelector('button').addEventListener('click'() => {
            throw new Error('err'// uncaught
          })

          只能通過函數(shù)體內(nèi) try catch 來捕獲。

          精讀

          我們開篇提到了要監(jiān)控所有異常,僅通過 try catch、then 捕獲同步、異步錯誤還是不夠的,因為這些是局部錯誤捕獲手段,當(dāng)我們無法保證所有代碼都處理了異常時,需要進行全局異常監(jiān)控,一般有兩種方法:

          • window.addEventListener('error')
          • window.addEventListener('unhandledrejection')

          error 可以監(jiān)聽所有同步、異步的運行時錯誤,但無法監(jiān)聽語法、接口、資源加載錯誤。而 unhandledrejection 可以監(jiān)聽到 Promise 中拋出的,未被 .catch 捕獲的錯誤。

          在具體的前端框架中,也可以通過框架提供的錯誤監(jiān)聽方案解決部分問題,比如 React 的 Error Boundaries、Vue 的 error handler,一個是 UI 組件級別的,一個是全局的。

          回過頭來看,本身 js 提供的 try catch 錯誤捕獲是非常有效的,之所以會遇到無法捕獲錯誤的經(jīng)常,大多是因為異步導(dǎo)致的。

          然而大部分異步錯誤,都可以通過 await 的方式解決,我們唯一要注意的是,await 僅支持一層,或者說一條鏈的錯誤監(jiān)聽,比如這個例子是可以監(jiān)聽到錯誤的:

          try {
            await func1()
          catch (err) {
            // caught
          }

          async function func1({
            await func2()
          }

          async function func2({
            throw Error('error')
          }

          也就是說,只要這一條鏈內(nèi)都被 await 住了,那么最外層的 try catch 就能捕獲異步錯誤。但如果有一層異步又脫離了 await,那么就無法捕獲了:

          async function func2({
            setTimeout(() => {
              throw Error('error'// uncaught
            })
          }

          針對這個問題,原文也提供了例如 Promise.all、鏈?zhǔn)?Promise、.catch 等方法解決,因此只要編寫代碼時注意對異步的處理,就可以用 try catch 捕獲這些異步錯誤。

          總結(jié)

          關(guān)于異步錯誤的處理,如果還有其它未考慮到的情況,歡迎留言補充。

          討論地址是:精讀《捕獲所有異步 error》· Issue #350 · dt-fe/weekly

          Node 社群


          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


             “分享、點贊在看” 支持一波??

          瀏覽 24
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  天天干天天干天天天干 | 丁香五月婷婷激情四射 | 爱搞视频网站 | 国产黄色A级毛片 | 夜夜躁日日躁狠狠久久AV |