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

          剖析前端異常及其降級處理和防范方案

          共 16426字,需瀏覽 33分鐘

           ·

          2021-07-16 00:24

          點(diǎn)擊上方 前端Q,關(guān)注公眾號

          回復(fù)加群,加入前端Q技術(shù)交流群

          一、導(dǎo)讀

          “異常”一詞出自《后漢書.卷一.皇后紀(jì)上.光烈陰皇后紀(jì)》,表示非正常的,不同于平常的。在我們現(xiàn)實(shí)生活中同樣處處存在著異常,比如小縣城里的路燈年久失修...,上下班高峰期深圳的地鐵總是那么的擁擠...,人也總是時(shí)不時(shí)會生病等等; 由此可見,這個(gè)世界錯(cuò)誤無處不在,這是一個(gè)基本的事實(shí)。

          而在計(jì)算機(jī)的世界中,異常指的是在程序運(yùn)行過程中發(fā)生的異常事件,有些錯(cuò)誤是由于外部環(huán)境導(dǎo)致的,有些錯(cuò)誤是由于開發(fā)人員疏忽所導(dǎo)致的,有效的處理這些錯(cuò)誤,保證計(jì)算機(jī)世界的正常運(yùn)轉(zhuǎn)是我們開發(fā)人員必不可少的一環(huán)。

          二、背景

          隨著項(xiàng)目的不斷壯大,客戶的不斷接入,項(xiàng)目的穩(wěn)定性成為團(tuán)隊(duì)的一大挑戰(zhàn)。

          當(dāng)用戶或者團(tuán)隊(duì)測試人員遇到問題時(shí),大概率是直接丟給開發(fā)人員一張白屏頁面或錯(cuò)誤UI的截圖,且該錯(cuò)誤并不是必現(xiàn)時(shí),讓前后端同學(xué)定位問題倍感頭痛。有沒有一種方式既能夠提升用戶體驗(yàn),又能夠幫助開發(fā)人員快速定位解決問題?

          本著“客戶就是上帝”的商業(yè)準(zhǔn)則,為用戶創(chuàng)造良好的用戶體驗(yàn),是前端開發(fā)者職責(zé)之所在。當(dāng)頁面發(fā)生錯(cuò)誤的時(shí)候,相比于頁面崩潰或點(diǎn)不動,在適當(dāng)?shù)臅r(shí)機(jī),以一種適當(dāng)?shù)姆绞饺ヌ嵝延脩舢?dāng)前發(fā)生了什么,無疑是一種更友好的處理方式。

          項(xiàng)目中面臨下面幾種異常場景,需要處理:

          • 語法錯(cuò)誤
          • 事件異常
          • HTTP請求異常
          • 靜態(tài)資源加載異常
          • Promise 異常
          • Iframe 異常
          • 頁面崩潰

          整體異常處理方案需要實(shí)現(xiàn)二方面的效果:

          1. 提升用戶體驗(yàn)
          2. 上報(bào)監(jiān)控系統(tǒng),能及時(shí)早發(fā)現(xiàn)、定位、解決問題

          下面我們先從幾個(gè)異常場景出發(fā),逐步探討如何解決這些異常并給予更好的用戶體驗(yàn)。

          三、錯(cuò)誤類型

          在探討具體的解決方案之前,我們先來認(rèn)識和熟悉一下前端的各種錯(cuò)誤類型。

          ECMA-262規(guī)范定義的七種錯(cuò)誤類型:

          • Error
          • EvalError
          • RangeError
          • ReferenceError
          • SyntaxError
          • TypeError
          • URIError

          Error

          Error是所有錯(cuò)誤的基類,其他錯(cuò)誤都繼承自該類型

          EvalError

          EvalError對象表示全局函數(shù)eval()中發(fā)生的錯(cuò)誤。如果eval()中沒有錯(cuò)誤,則不會拋出該錯(cuò)誤。可以通過構(gòu)造函數(shù)創(chuàng)建這個(gè)對象的實(shí)例

          image.png

          RangeError

          RangeError對象表示當(dāng)一個(gè)值不在允許值的集合或范圍內(nèi)時(shí)出現(xiàn)錯(cuò)誤。

          image.png

          ReferenceError

          當(dāng)引用不存在的變量時(shí),該對象表示錯(cuò)誤:

          image.png

          SyntaxError

          當(dāng)JavaScript引擎在解析代碼時(shí)遇到不符合該語言語法的標(biāo)記或標(biāo)記順序時(shí),將引發(fā)該異常:

          image.png

          TypeError

          傳遞給函數(shù)的操作數(shù)或?qū)崊⑴c該操作符或函數(shù)期望的類型不兼容:

          image.png

          URIError

          當(dāng)全局URI處理函數(shù)以錯(cuò)誤的方式使用時(shí):

          image.png

          四、處理和防范

          上文我們提到錯(cuò)誤和異常無處不在,存在于各式各樣的應(yīng)用場景中,那我們應(yīng)該如何有效的攔截異常,將錯(cuò)誤扼殺于搖籃之中,讓用戶無感呢?亦或者遇到致命錯(cuò)誤時(shí),進(jìn)行降級處理?

          (1) try catch

          1.語法

          ECMA-262 第 3 版中引入了 try-catch作為 JavaScript 中處理異常的一種標(biāo)準(zhǔn)方式,基本的語法如下所示。

          try {
            // 可能會導(dǎo)致錯(cuò)誤的代碼
          } catch (error) {
            // 在錯(cuò)誤發(fā)生時(shí)怎么處理
          }
          復(fù)制代碼

          2.動機(jī)

          使用try...catch來捕獲異常,我歸納起來主要有兩個(gè)動機(jī):

          1)是真真正正地想對可能發(fā)生錯(cuò)誤的代碼進(jìn)行異常捕獲;

          2)我想保證后面的代碼繼續(xù)運(yùn)行。

          動機(jī)一沒什么好講的,在這里,我們講講動機(jī)二。假如我們有以下代碼:

          console.log(foo); //foo未定義
          console.log('I want running')
          復(fù)制代碼

          代碼一執(zhí)行,你猜怎么著?第一行語句報(bào)錯(cuò)了,第二行語句的log也就沒打印出來。如果我們把代碼改成這樣:

          try{ 
              console.log(foo)
          }catch(e){
              console.log(e)
          }
          console.log('I want running');
          復(fù)制代碼

          以上代碼執(zhí)行之后,雖然還是報(bào)了個(gè)ReferenceError錯(cuò)誤,但是后面的log卻能夠被執(zhí)行。

          從這個(gè)示例,我們可以看出,一旦前面的(同步)代碼出現(xiàn)了沒有被開發(fā)者捕獲的異常的話,那么后面的代碼就不會執(zhí)行了。所以,如果你希望當(dāng)前可能出錯(cuò)的代碼塊后續(xù)的代碼能夠正常運(yùn)行的話,那么你就得使用try...catch來主動捕獲異常。

          擴(kuò)展:

          實(shí)際上,出錯(cuò)代碼是如何干擾后續(xù)代碼的執(zhí)行,是一個(gè)值得探討的主題。下面進(jìn)行具體的探討。因?yàn)槭忻嫔蠟g覽器眾多,對標(biāo)準(zhǔn)的實(shí)現(xiàn)也不太一致。所以,這里的結(jié)論僅僅是基于Chromev91.0.4472.114。探討過程中,我們涉及到兩組概念:同步代碼與異步代碼,代碼書寫期和代碼運(yùn)行期。

          場景一:同步代碼(出錯(cuò)) + 同步代碼

          1625024247(1).png

          可以看到,出錯(cuò)的同步代碼后面的同步代碼不執(zhí)行了。

          場景二:同步代碼(出錯(cuò)) + 異步代碼

          1625024396(1).png

          跟上面的情況一下,異步代碼也受到影響,也不執(zhí)行了。

          場景三:異步代碼(出錯(cuò)) + 同步代碼

          image.png

          可以看到,異步代碼出錯(cuò),并不會影響后面同步代碼的執(zhí)行。

          場景四:異步代碼(出錯(cuò)) + 異步代碼

          image.png

          出錯(cuò)的異步代碼也不會影響后面異步代碼的執(zhí)行。

          如果只看場景一二三,很容易得出如下結(jié)論:在代碼運(yùn)行期,同步代碼始終是先于異步代碼執(zhí)行的。如果先執(zhí)行的同步代碼沒有出錯(cuò)的話,那么后面的代碼就會正常執(zhí)行,否則后面的代碼就不會執(zhí)行。但場景四卻打破了這個(gè)結(jié)論。我們不妨繼續(xù)看看場景五。

          場景五:異步代碼 + 同步代碼(出錯(cuò)) + 異步代碼

          image.png

          看到了沒?同樣是異步代碼,按理說,代碼運(yùn)行期,如果你是受出錯(cuò)的同步代碼的影響的話,那你要么是兩個(gè)都不執(zhí)行,或者兩個(gè)都執(zhí)行啊?憑什么寫在出錯(cuò)代碼代碼書寫期前面的異步代碼就能正常執(zhí)行,而寫在后面的就不執(zhí)行呢?經(jīng)過驗(yàn)證,在firefoxv75.0版本中也是同樣的表現(xiàn)。

          所以,到了這里,我們基本上可以得出這樣的結(jié)論:運(yùn)行期,一先一后的兩個(gè)代碼中,出錯(cuò)的一方代碼是如何影響另外一方代碼繼續(xù)執(zhí)行的問題中,跟異步代碼沒關(guān)系,只跟同步代碼有關(guān)系;跟代碼執(zhí)行期沒關(guān)系,只跟代碼書寫期有關(guān)系。

          說人話就是,異步代碼出錯(cuò)與否都不會影響其他代碼繼續(xù)執(zhí)行。而出錯(cuò)的同步代碼,如果它在代碼書寫期是寫在其他代碼之前,并且我們并沒有對它進(jìn)行手動地去異常捕獲的話,那么它就會影響其他代碼(不論它是同步還是異步代碼)的繼續(xù)執(zhí)行。

          綜上所述,如果我們想要保證某塊可能出錯(cuò)的同步代碼后面的代碼繼續(xù)執(zhí)行的話,那么我們必須對這塊同步代碼進(jìn)行異常捕獲。

          3.范圍

          只能捕獲同步代碼所產(chǎn)生的運(yùn)行時(shí)錯(cuò)誤,對于語法錯(cuò)誤和異步代碼所產(chǎn)生的錯(cuò)誤是無能為力的。

          當(dāng)遇到語法錯(cuò)誤時(shí):

          當(dāng)遇到異步運(yùn)行時(shí)錯(cuò)誤時(shí):

          (2) Promise.catch()

          1.語法

          const promise1 = new Promise((resolve, reject) => {
            throw 'Uh-oh!';
          });

          promise1.catch((error) => {
            console.error(error);
          });
          // expected output: Uh-oh!
          復(fù)制代碼

          2.動機(jī)

          用來捕獲promise代碼中的錯(cuò)誤

          3.范圍

          使用Promise.prototype.catch()我們可以方便的捕獲到異常,現(xiàn)在我們來測試一下常見的語法錯(cuò)誤、代碼錯(cuò)誤以及異步錯(cuò)誤。

          當(dāng)遇到代碼錯(cuò)誤時(shí),可以捕獲:

          當(dāng)遇到語法錯(cuò)誤時(shí),不能捕獲:

          當(dāng)遇到異步運(yùn)行時(shí)錯(cuò)誤時(shí),不能捕獲:

          1625033576(1).png

          (3) unhandledrejection

          1.用法

          unhandledrejection:當(dāng)Promise 被 reject 且沒有 reject 處理器的時(shí)候,會觸發(fā) unhandledrejection 事件

          window.addEventListener("unhandledrejection"function(e){
            console.log(e);
          });
          復(fù)制代碼

          2.動機(jī)

          為了防止有漏掉的 Promise 異常,可以在全局增加一個(gè)對 unhandledrejection 的監(jiān)聽進(jìn)行兜底,用來全局監(jiān)聽Uncaught Promise Error。

          3.范圍

              window.addEventListener("unhandledrejection"function (e) {
                console.log("捕獲到的promise異常:", e);
                e.preventDefault();
              });
              new Promise((res) => {
                console.log(a);
              });
              // 捕獲到的promise異常的: PromiseRejectionEvent
          復(fù)制代碼

          注意:此段代碼直接寫在控制臺是捕獲不到promise異常的,寫在html文件中可正常捕獲。

          (4) window.onerror

          1.用法

          當(dāng) JS 運(yùn)行時(shí)錯(cuò)誤發(fā)生時(shí),window 會觸發(fā)一個(gè) ErrorEvent 接口的 error 事件,并執(zhí)行 window.onerror()。

          window.onerror = function(message, source, lineno, colno, error) {
             console.log('捕獲到異常:',{message, source, lineno, colno, error});
          }
          復(fù)制代碼

          2.動機(jī)

          眾所周知,很多做錯(cuò)誤監(jiān)控和上報(bào)的類庫就是基于這個(gè)特性來實(shí)現(xiàn)的,我們期待它能處理那些try...catch不能處理的錯(cuò)誤。

          3.范圍

          根據(jù)MDN的說法,wondow.onerror能捕獲JavaScript運(yùn)行時(shí)錯(cuò)誤(包括語法錯(cuò)誤)或一些資源錯(cuò)誤。而在真正的測試過程中,wondow.onerror并不能捕獲語法錯(cuò)誤。

          image.png

          經(jīng)測試,window.onerror并不能捕獲語法錯(cuò)誤和靜態(tài)資源的加載錯(cuò)誤。同樣也不能捕獲異步代碼的錯(cuò)誤,但是有一點(diǎn)值得注意的是,window.onerror能捕獲同樣是異步代碼的setTimeout和setInterval里面的錯(cuò)誤。

          看來,寄予厚望的window.onerror并不是萬能的。

          (5) window.addEventListener

          1.用法

          window.addEventListener('error',(error)=>{console.log(error)})
          復(fù)制代碼

          2.動機(jī)

          當(dāng)然是希望用他來兜住window.onerror和try catch的底,希望他能捕獲到異步錯(cuò)誤和資源的加載錯(cuò)誤。

          3.范圍

            <body>
              <img id="img" src="./fake.png" />
              <iframe id="iframe" src="./test4.html"></iframe>
            </body>
            <script>
              window.addEventListener(
                "error",
                function (error) {
                  console.log(error, "error");
                },
                true
              );
              setTimeout(() => {
                console.log(a);
              });
              new Promise((resolve, reject) => {
                console.log(a);
              });
              console.log(b)
              var f=e, //語法異常
            </script>
          復(fù)制代碼

          在此過程中,資源文件都是不存在的,我們發(fā)現(xiàn)window.addEventListener('error')依舊不能捕獲語法錯(cuò)誤,Promise異常和iframe異常。

          對于語法錯(cuò)誤我們可以在編譯過程中捕獲,,Promise異常已在上文中給出解決方案,現(xiàn)在還剩下iframe異常需要單獨(dú)處理了。

          (5) iframe異常

          1.用法

          window.frames[0].onerror = function (message, source, lineno, colno, error) {
              console.log('捕獲到 iframe 異常:',{message, source, lineno, colno, error});
              return true;
          };
          復(fù)制代碼

          2.動機(jī)

          用來專門捕獲iframe加載過程中的異常。

          3.范圍

          很遺憾,結(jié)果并不令人滿意,在實(shí)際的測試過程中,該方法未能捕獲到異常。

          (6) React中捕獲異常

          部分 UI 的 JavaScript 錯(cuò)誤不應(yīng)該導(dǎo)致整個(gè)應(yīng)用崩潰,為了解決這個(gè)問題,React 16 引入了一個(gè)新的概念 —— 錯(cuò)誤邊界。

          錯(cuò)誤邊界是一種 React 組件,這種組件可以捕獲并打印發(fā)生在其子組件樹任何位置的 JavaScript 錯(cuò)誤,并且,它會渲染出備用 UI,而不是渲染那些崩潰了的子組件樹。錯(cuò)誤邊界在渲染期間、生命周期方法和整個(gè)組件樹的構(gòu)造函數(shù)中捕獲錯(cuò)誤。

          注意:錯(cuò)誤邊界無法捕獲以下場景中產(chǎn)生的錯(cuò)誤

          • 事件處理
          • 異步代碼(例如 setTimeout 或 requestAnimationFrame 回調(diào)函數(shù))
          • 服務(wù)端渲染
          • 它自身拋出來的錯(cuò)誤(并非它的子組件)

          如果一個(gè) class 組件中定義了 static getDerivedStateFromError() 或 componentDidCatch() 這兩個(gè)生命周期方法中的任意一個(gè)(或兩個(gè))時(shí),那么它就變成一個(gè)錯(cuò)誤邊界。當(dāng)拋出錯(cuò)誤后,請使用 static getDerivedStateFromError() 渲染備用 UI ,使用 componentDidCatch() 打印錯(cuò)誤信息。

          class ErrorBoundary extends React.Component {
            constructor(props) {
              super(props);
              this.state = { hasError: false };
            }

            static getDerivedStateFromError(error) {
              // 更新 state 使下一次渲染能夠顯示降級后的 UI
              return { hasError: true };
            }

            componentDidCatch(error, errorInfo) {
              // 你同樣可以將錯(cuò)誤日志上報(bào)給服務(wù)器
              logErrorToMyService(error, errorInfo);
            }

            render() {
              if (this.state.hasError) {
                // 你可以自定義降級后的 UI 并渲染
                return <h1>Something went wrong.</h1>;
              }

              return this.props.children; 
            }
          }
          復(fù)制代碼

          錯(cuò)誤邊界的工作方式類似于 JavaScript 的 catch {},不同的地方在于錯(cuò)誤邊界只針對 React 組件。只有 class 組件才可以成為錯(cuò)誤邊界組件。大多數(shù)情況下, 你只需要聲明一次錯(cuò)誤邊界組件, 并在整個(gè)應(yīng)用中使用它。

          以上引用自React 官網(wǎng)。

          (7) Vue中捕獲異常

          Vue.config.errorHandler = function (err, vm, info) {
            // handle error
            // `info` 是 Vue 特定的錯(cuò)誤信息,比如錯(cuò)誤所在的生命周期鉤子
            // 只在 2.2.0+ 可用
          }
          復(fù)制代碼

          指定組件的渲染和觀察期間未捕獲錯(cuò)誤的處理函數(shù)。這個(gè)處理函數(shù)被調(diào)用時(shí),可獲取錯(cuò)誤信息和 Vue 實(shí)例。

          • 從 2.2.0 起,這個(gè)鉤子也會捕獲組件生命周期鉤子里的錯(cuò)誤。同樣的,當(dāng)這個(gè)鉤子是 undefined 時(shí),被捕獲的錯(cuò)誤會通過 console.error 輸出而避免應(yīng)用崩潰。
          • 從 2.4.0 起,這個(gè)鉤子也會捕獲 Vue 自定義事件處理函數(shù)內(nèi)部的錯(cuò)誤了。
          • 從 2.6.0 起,這個(gè)鉤子也會捕獲 v-on DOM 監(jiān)聽器內(nèi)部拋出的錯(cuò)誤。另外,如果任何被覆蓋的鉤子或處理函數(shù)返回一個(gè) Promise 鏈 (例如 async 函數(shù)),則來自其 Promise 鏈的錯(cuò)誤也會被處理。

          以上引用自Vue 官網(wǎng)。

          (8) http請求異常

          1.用法

          以axios為例,添加響應(yīng)攔截器

          axios.interceptors.response.use(function (response) {
              // 對響應(yīng)數(shù)據(jù)做點(diǎn)什么
              // response 是請求回來的數(shù)據(jù)
              return response;
            }, function (error) {
              // 對響應(yīng)錯(cuò)誤做點(diǎn)什么
              return Promise.reject(error)
            }
          )

          復(fù)制代碼

          2.動機(jī)

          用來專門捕獲HTTP請求異常

          五、項(xiàng)目實(shí)踐

          在提出了這么多的解決方案之后,相信大家對具體怎么用還是存在一些疑惑。那么接下來,我們真正的進(jìn)入實(shí)踐階段吧!

          我們再次回顧一下我們需要解決的問題是什么?

          • 語法錯(cuò)誤
          • 事件異常
          • HTTP請求異常
          • 靜態(tài)資源加載異常
          • Promise 異常
          • Iframe 異常
          • 頁面崩潰

          捕獲異常是我們的最終目標(biāo)嗎?并不是,回到解決問題的背景下,相比于頁面崩潰或點(diǎn)不動,在適當(dāng)?shù)臅r(shí)機(jī),以一種適當(dāng)?shù)姆绞饺ヌ嵝延脩舢?dāng)前發(fā)生了什么,無疑是一種更友好的處理方式。

          結(jié)合到項(xiàng)目中,具體實(shí)踐起來有如下兩種方案:

          • 1.代碼中通過大量的try catch/Promise.catch來捕獲,捕獲不到的使用其他方式進(jìn)行兜底
          • 2.通過框架提供的機(jī)制來做,再對不能捕獲的進(jìn)行兜底

          方案一無疑不是很聰明的樣子...這意味著要去改大量的原有代碼,心智負(fù)擔(dān)成倍數(shù)增加。方案二則更加明智,通過在底層對錯(cuò)誤進(jìn)行統(tǒng)一處理,無需變更原有邏輯。

          到項(xiàng)目中,使用的是React框架,React正好提供了一種捕獲異常的機(jī)制(上文已提及)并做降級處理,但是細(xì)心的小伙伴發(fā)現(xiàn)了,react并不能捕獲如下四種錯(cuò)誤:

          • 事件處理
          • 異步代碼(例如 setTimeout 或 requestAnimationFrame 回調(diào)函數(shù))
          • 服務(wù)端渲染
          • 它自身拋出來的錯(cuò)誤(并非它的子組件)

          對于第三點(diǎn)服務(wù)端渲染錯(cuò)誤,項(xiàng)目中并沒有適用的場景,此次不做重點(diǎn)分析。我們重點(diǎn)分析第一點(diǎn)和第二點(diǎn)。

          我在這里先拋出幾個(gè)問題,大家先做短暫的思考:

          • 1.若事件處理和異步代碼的錯(cuò)誤導(dǎo)致頁面crash,我們該如何預(yù)防?
          • 2.如何對ErrorBounary進(jìn)行兜底?相比一個(gè)按鈕點(diǎn)擊無效,如何更加友好的提示用戶?

          先來看第一個(gè)問題,若事件處理和異步代碼的錯(cuò)誤導(dǎo)致頁面崩潰:

          const Test = () => {
            const [data, setData] = useState([]);
            return (
              <div
                onClick={() => {
                  setData('');
                }}
              >
                {data.map((s) => s.i)}
              </div>
            );
          };
          復(fù)制代碼

          此段代碼在正常渲染期間是沒問題的,但在觸發(fā)了點(diǎn)擊事件之后會導(dǎo)致頁面異常白屏,如果在外面套上我們的ErrorBounday組件,情況會是怎么樣呢?

          答案是依然能夠捕獲到錯(cuò)誤,并能夠?qū)υ摻M件進(jìn)行降級處理!

          此時(shí)有些小伙伴已經(jīng)察覺到了,錯(cuò)誤邊界只要是在渲染期間都是可以捕獲錯(cuò)誤的,無論首次渲染還是二次渲染。流程圖如下:

          image.png

          第一個(gè)問題原來根本就不是問題,這本身就是一個(gè)閉環(huán),不用我們解決!

          再來看看第二個(gè)問題:

          對于事件處理和異步代碼中不會導(dǎo)致頁面崩潰的代碼:

          const Test = () => {
            return (
              <button
                onClick={() => {
                  [].map((s) => s.a.b);
                }}
              >
                點(diǎn)擊
              </button>
            );
          };
          復(fù)制代碼

          button按鈕可正常點(diǎn)擊,但是該點(diǎn)擊事件的內(nèi)部邏輯是有問題的,導(dǎo)致用戶點(diǎn)擊該按鈕本質(zhì)是無效的。此時(shí)若不及時(shí)給與友好提示,用戶只會陷入抓狂中....

          那么有沒有辦法對ErrorBoundary進(jìn)行兜底呢?即可以捕獲異步代碼或事件處理中的錯(cuò)誤。

          上文提到的window.addEventListener('error')正好可以解決這個(gè)問題。理想狀態(tài)下:

          而真正的執(zhí)行順序確實(shí)這樣的:

          1625105438(1).png

          在真正執(zhí)行的過程中,window.addEventListener('error')是先于ErrorBoundary捕獲到錯(cuò)誤的,這就導(dǎo)致當(dāng)error事件捕獲到錯(cuò)誤時(shí),他并不知道該錯(cuò)誤是否會導(dǎo)致頁面崩潰,不知道該給予怎樣的提示,到底是對頁面進(jìn)行降級處理還是只做簡單的報(bào)錯(cuò)提示?

          問題似乎就卡在這了....

          那能否通過一種有效的途徑告訴error事件:ErrorBoundary已經(jīng)捕獲到了錯(cuò)誤,你不需要處理!亦或者是ErrorBoundary未能捕獲到錯(cuò)誤,這是一個(gè)異步錯(cuò)誤/事件錯(cuò)誤,但不會引起頁面崩潰,你只需要提示用戶!

          答案肯定是有的,比如建立一個(gè)nodeJs服務(wù)器,通過webSocket去通知,但是這樣做不僅麻煩,還會有一定的延遲。

          在筆者苦思冥想之際,在某個(gè)靜悄悄的夜晚,突然靈感一現(xiàn)。為什么我們非要按照他規(guī)定的順序執(zhí)行呢?我們能不能嘗試改變他的執(zhí)行順序,讓錯(cuò)誤捕獲回到我們理想中的流程來呢?

          改變思路之后,我們再思考有什么能改變代碼執(zhí)行順序嗎?沒錯(cuò),異步事件!

                window.addEventListener('error'function (error) {
                  setTimeout(()=>{
                    console.log(error, 'error錯(cuò)誤');
                  })
                });
          復(fù)制代碼

          當(dāng)給error事件的回調(diào)函數(shù)加入setTimeout后,捕獲異常的流程為:

          image.png

          現(xiàn)在就可以通知error事件到底頁面崩潰了沒有,到底需不需要它的處理!上代碼:

          class ErrorBoundary extends React.Component {
            constructor(props) {
              super(props);
              this.state = { hasError: false };
            }

            static getDerivedStateFromError(error) {
              // 更新 state 使下一次渲染能夠顯示降級后的 UI
              return { hasError: true };
            }

            componentDidCatch(error, errorInfo) {
              // 你同樣可以將錯(cuò)誤日志上報(bào)給服務(wù)
              logErrorToMyService(error, errorInfo);
              
              //告訴error事件 ErrorBoundary已處理異常
               localStorage.setItem("ErrorBoundary",true)
            }

            render() {
              if (this.state.hasError) {
                // 你可以自定義降級后的 UI 并渲染
                return <h1>Something went wrong.</h1>;
              }

              return this.props.children; 
            }
          }
          復(fù)制代碼
            window.addEventListener('error'function (error) {
                  setTimeout(() => {
                    //進(jìn)來代表一定有錯(cuò)誤 判斷ErrorBoundary中是否已處理異常
                    const flag = localStorage.getItem('ErrorBounary');
                    if (flag) {
                      //進(jìn)入了ErrorBounary 錯(cuò)誤已被處理 error事件不用處理該異常
                      localStorage.setItem('ErrorBounary'false); //重置狀態(tài)
                    } else {
                      //未進(jìn)入ErrorBounary 代表此錯(cuò)誤為異步錯(cuò)誤/事件錯(cuò)誤
                      logErrorToMyService(error, errorInfo);  // 你可以將錯(cuò)誤日志上報(bào)給服務(wù)
                      //判斷具體錯(cuò)誤類型
                      if (error.message.indexOf('TypeError')) {
                        alert('這是一個(gè)TypeError錯(cuò)誤,請通知開發(fā)人員');
                      } else if (error.message.indexOf('SyntaxError')) {
                        alert('這是一個(gè)SyntaxError錯(cuò)誤,請通知開發(fā)人員');
                      } else {
                        //在此次給與友好提示
                      }
                    }
                  });
                });
          復(fù)制代碼

          最后,通過我們的努力,當(dāng)頁面崩潰時(shí),及時(shí)進(jìn)行降級處理;當(dāng)頁面未崩潰,但有錯(cuò)誤時(shí),我們及時(shí)的告知用戶,并對錯(cuò)誤進(jìn)行上報(bào),達(dá)到預(yù)期的效果。

          六、擴(kuò)展

          1.設(shè)置采集率

          若是錯(cuò)誤實(shí)在太多,比如有時(shí)候代碼進(jìn)入死循環(huán),錯(cuò)誤量過多導(dǎo)致服務(wù)器壓力大時(shí),可酌情降低采集率。比如采集30%:

                if (Math.random() < 0.3) {
                  //上報(bào)錯(cuò)誤
                  logErrorToMyService(error, errorInfo);
                }
          復(fù)制代碼

          2.提效

          解決上面這些問題后,大家難免會有疑問:那每一個(gè)組件都要去套一層ErrorBoundary組件,這工作量是不是有點(diǎn)大....而且有一些老代碼,嵌套的比較深,改起來心理負(fù)擔(dān)也會比較大。那有沒有辦法將其作為一個(gè)配置項(xiàng),配置完之后,編譯時(shí)自動套上一層ErrorBoundary組件呢?這個(gè)我們下次在做探討!

          3.可配置

          能否將ErrorBoundary擴(kuò)展成可傳入自定義UI的組件呢?這樣大家通過定制化UI,在不同的場景進(jìn)行不同的降級處理。

          同樣,這一塊我們下次再討論!

          七、總結(jié)

          異常處理是高質(zhì)量軟件開發(fā)中的一個(gè)基本部分,但是在許多情況下,它們會被忽略,或者是不正確的使用,而處理異常只是保證代碼流程不出錯(cuò),重定向到正確的程序流中去。

          本文從前端錯(cuò)誤類型出發(fā),從try catch逐步揭開錯(cuò)誤異常神秘的面紗,再通過一系列的操作對異常進(jìn)行監(jiān)控和捕獲,最后達(dá)到提升用戶體驗(yàn),上報(bào)監(jiān)控系統(tǒng)的效果。

          八、思考

          • Promise.catch 和 try catch 捕獲異常有什么區(qū)別?
          • ErrorBounary內(nèi)部如何實(shí)現(xiàn)?
          • 為什么unhandledrejection寫在控制臺是捕獲不到錯(cuò)誤的?而寫在HTML文件中就可以捕獲到?
          • 服務(wù)端渲染錯(cuò)誤如何捕獲?

          帶著這些思考,我們下次見~

          關(guān)于本文

          來源:縱有疾風(fēng)起

          https://juejin.cn/post/6979564690787532814



          內(nèi)推社群


          我組建了一個(gè)氛圍特別好的騰訊內(nèi)推社群,如果你對加入騰訊感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行面試相關(guān)的答疑、聊聊面試的故事、并且在你準(zhǔn)備好的時(shí)候隨時(shí)幫你內(nèi)推。下方加 winty 好友回復(fù)「面試」即可。


          瀏覽 31
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  久久无码图片 | 北条麻妃一区二区三区蜜臀色欲 | 色婷婷五月天亚洲中文字幕 | 色女人导航 | 97视频中文自拍 |