1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 6848字,需瀏覽 14分鐘

         ·

        2021-10-26 01:55

        大廠技術(shù)  高級(jí)前端  Node進(jìn)階

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

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

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

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

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

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

        概述

        之所以要關(guān)注異步異常,是因?yàn)椴东@同步異常非常簡(jiǎn)單:

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

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

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

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

        要捕獲 async 函數(shù)內(nèi)的異常,可以調(diào)用 .catch,因?yàn)?async 函數(shù)返回一個(gè) 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)再包含一個(gè)異步:

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

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

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

        另一種情況是,這個(gè) 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 后拋出異常,但因?yàn)?2s 后拋出了 err2 異常,中斷了代碼執(zhí)行,所以 await p1 不會(huì)被執(zhí)行到,導(dǎo)致這個(gè)異常不會(huì)被 catch 住。

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

        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 后會(huì)拋出一個(gè)未捕獲異常,但再過 1s 這個(gè)未捕獲異常就消失了,變成了捕獲的異常。

        這個(gè)行為很奇怪,當(dāng)程序復(fù)雜時(shí)很難排查,因?yàn)椴⑿械?Promise 建議用 Promise.all 處理:

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

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

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

        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)部多次異步嵌套,這樣多個(gè)異步行為會(huì)被拆解為對(duì)應(yīng) Promise Chain 的同步行為,Promise 就可以捕獲啦。

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

        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 捕獲同步、異步錯(cuò)誤還是不夠的,因?yàn)檫@些是局部錯(cuò)誤捕獲手段,當(dāng)我們無法保證所有代碼都處理了異常時(shí),需要進(jìn)行全局異常監(jiān)控,一般有兩種方法:

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

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

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

        回過頭來看,本身 js 提供的 try catch 錯(cuò)誤捕獲是非常有效的,之所以會(huì)遇到無法捕獲錯(cuò)誤的經(jīng)常,大多是因?yàn)楫惒綄?dǎo)致的。

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

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

        async function func1({
          await func2()
        }

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

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

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

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

        總結(jié)

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

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

        Node 社群


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


           “分享、點(diǎn)贊、在看” 支持一波??

        瀏覽 24
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            中国免费毛片 | 国产在线播放91 | 亚洲欧洲精品成人久久奇米网 | 成人免费无遮挡无码黄漫视频 | 日日摸天天做天天添天天欢 | 成人大香蕉网站精品免费 | 日本一级婬片A片免费播放一 | 91自产国偷拍在线 | 亚洲欧洲综合 | 国产又大又粗又爽免费看 |