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>

        精讀《捕獲所有異步 error》

        共 6835字,需瀏覽 14分鐘

         ·

        2021-09-14 01:03

         大廠技術(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」即可。


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

        瀏覽 58
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        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>
            成人电影日韩 | 国产V亚洲V天堂无码精品 | 粉嫩小泬BBBB免费看 | 亚洲999 | 潘金莲三级做爰 | 八人轮换和9人轮换的区别 | 男人日女人视频软件 | 国产伦精品一区二区三区视频猫咪 | 美女黄视频网站 | 中文字幕视频在线 |