亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

有哪些前端異常處理

發布時間:2021-11-02 16:46:10 來源:億速云 閱讀:142 作者:iii 欄目:web開發

這篇文章主要講解了“有哪些前端異常處理”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“有哪些前端異常處理”吧!

什么是異常

用直白的話來解釋異常的話,就是程序發生了意想不到的情況,這種情況影響到了程序的正確運行。

從根本上來說,異常就是一個數據結構,其保存了異常發生的相關信息,比如錯誤碼,錯誤信息等。以 JS 中的標準內置對象 Error 為例,其標準屬性有 name 和 message。然而不同的瀏覽器廠商有自己的自定義屬性,這些屬性并不通用。比如 Mozilla 瀏覽器就增加了 filename 和 stack 等屬性。

值得注意的是錯誤只有被拋出,才會產生異常,不被拋出的錯誤不會產生異常。比如:

function t() {    console.log("start");    new Error();    console.log("end");  }  t();

有哪些前端異常處理

(動畫演示)

這段代碼不會產生任何的異常,控制臺也不會有任何錯誤輸出。

異常的分類

按照產生異常時程序是否正在運行,我們可以將錯誤分為編譯時異常和運行時異常。

編譯時異常指的是源代碼在編譯成可執行代碼之前產生的異常。而運行時異常指的是可執行代碼被裝載到內存中執行之后產生的異常。

編譯時異常

我們知道 TS 最終會被編譯成 JS,從而在 JS Runtime中執行。既然存在編譯,就有可能編譯失敗,就會有編譯時異常。

比如我使用 TS 寫出了如下代碼:

const s: string = 123;

這很明顯是錯誤的代碼, 我給 s 聲明了 string 類型,但是卻給它賦值 number。

當我使用 tsc(typescript 編譯工具,全稱是 typescript compiler)嘗試編譯這個文件的時候會有異常拋出:

tsc a.ts  a.ts:1:7 - error TS2322: Type '123' is not assignable to type 'string'.  1 const s: string = 123;          ~  Found 1 error.

這個異常就是編譯時異常,因為我的代碼還沒有執行。

然而并不是你用了 TS 才存在編譯時異常,JS 同樣有編譯時異常。有的人可能會問 JS 不是解釋性語言么?是邊解釋邊執行,沒有編譯環節,怎么會有編譯時異常?

別急,我舉個例子你就明白了。如下代碼:

function t() {    console.log('start')    await sa    console.log('end')  }  t()

上面的代碼由于存在語法錯誤,不會編譯通過,因此并不會打印start,側面證明了這是一個編譯時異常。盡管 JS 是解釋語言,也依然存在編譯階段,這是必然的,因此自然也會有編譯異常。

總的來說,編譯異常可以在代碼被編譯成最終代碼前被發現,因此對我們的傷害更小。接下來,看一下令人心生畏懼的運行時異常。

運行時異常

相信大家對運行時異常非常熟悉。這恐怕是廣大前端碰到最多的異常類型了。眾所周知的 NPE(Null Pointer Exception) 就是運行時異常。

將上面的例子稍加改造,得到下面代碼:

function t() {    console.log("start");    throw 1;    console.log("end");  }  t();

有哪些前端異常處理

(動畫演示)

注意 end 沒有打印,并且 t 沒有彈出棧。實際上 t 最終還是會被彈出的,只不過和普通的返回不一樣。

如上,則會打印出start。由于異常是在代碼運行過程中拋出的,因此這個異常屬于運行時異常。相對于編譯時異常,這種異常更加難以發現。上面的例子可能比較簡單,但是如果我的異常是隱藏在某一個流程控制語句(比如 if else)里面呢?程序就可能在客戶的電腦走入那個拋出異常的 if 語句,而在你的電腦走入另一條。這就是著名的 《在我電腦上好好的》 事件。

異常的傳播

異常的傳播和我之前寫的瀏覽器事件模型有很大的相似性。只不過那個是作用在 DOM 這樣的數據結構,這個則是作用在函數調用棧這種數據結構,并且事件傳播存在捕獲階段,異常傳播是沒有的。不同 C 語言,JS 中異常傳播是自動的,不需要程序員手動地一層層傳遞。如果一個異常沒有被 catch,它會沿著函數調用棧一層層傳播直到棧空。

異常處理中有兩個關鍵詞,它們是throw(拋出異常) 和 catch(處理異常)。 當一個異常被拋出的時候,異常的傳播就開始了。異常會不斷傳播直到遇到第一個 catch。 如果程序員沒有手動 catch,那么一般而言程序會拋出類似unCaughtError,表示發生了一個異常,并且這個異常沒有被程序中的任何 catch 語言處理。未被捕獲的異常通常會被打印在控制臺上,里面有詳細的堆棧信息,從而幫助程序員快速排查問題。實際上我們的程序的目標是避免 unCaughtError這種異常,而不是一般性的異常。

一點小前提

由于 JS 的 Error 對象沒有 code 屬性,只能根據 message 來呈現,不是很方便。我這里進行了簡單的擴展,后面很多地方我用的都是自己擴展的 Error ,而不是原生 JS Error ,不再贅述。

oldError = Error;  Error = function ({ code, message, fileName, lineNumber }) {    error = new oldError(message, fileName, lineNumber);    error.code = code;    return error;  };

手動拋出 or 自動拋出

異常既可以由程序員自己手動拋出,也可以由程序自動拋出。

throw new Error(`I'm Exception`);

(手動拋出的例子)

a = null;  a.toString(); // Thrown: TypeError: Cannot read property 'toString' of null

(程序自動拋出的例子)

自動拋出異常很好理解,畢竟我們哪個程序員沒有看到過程序自動拋出的異常呢?

“這個異常突然就跳出來!嚇我一跳!”,某不知名程序員如是說。

那什么時候應該手動拋出異常呢?

一個指導原則就是你已經預知到程序不能正確進行下去了。比如我們要實現除法,首先我們要考慮的是被除數為 0 的情況。當被除數為 0 的時候,我們應該怎么辦呢?是拋出異常,還是 return 一個特殊值?答案是都可以,你自己能區分就行,這沒有一個嚴格的參考標準。 我們先來看下拋出異常,告訴調用者你的輸入,我處理不了這種情況。

function divide(a, b) {    a = +a;    b = +b; // 轉化成數字    if (!b) {      // 匹配 +0, -0, NaN      throw new Error({        code: 1,        message: "Invalid dividend " + b,      });    }    if (Number.isNaN(a)) {      // 匹配 NaN      throw new Error({        code: 2,        message: "Invalid divisor " + a,      });    }    return a / b;  }

上面代碼會在兩種情況下拋出異常,告訴調用者你的輸入我處理不了。由于這兩個異常都是程序員自動手動拋出的,因此是可預知的異常。

剛才說了,我們也可以通過返回值來區分異常輸入。我們來看下返回值輸入是什么,以及和異常有什么關系。

異常 or 返回

如果是基于異常形式(遇到不能處理的輸入就拋出異常)。當別的代碼調用divide的時候,需要自己 catch。

function t() {    try {      divide("foo", "bar");    } catch (err) {      if (err.code === 1) {        return console.log("被除數必須是除0之外的數");      }      if (err.code === 2) {        return console.log("除數必須是數字");      }      throw new Error("不可預知的錯誤");    }  }

然而就像上面我說的那樣,divide 函數設計的時候,也完全可以不用異常,而是使用返回值來區分。

function divide(a, b) {    a = +a;    b = +b; // 轉化成數字    if (!b) {      // 匹配 +0, -0, NaN      return new Error({        code: 1,        message: "Invalid dividend " + b,      });    }    if (Number.isNaN(a)) {      // 匹配 NaN      return new Error({        code: 2,        message: "Invalid divisor " + a,      });    }    return a / b;  }

當然,我們使用方式也要作出相應改變。

function t() {    const res = divide("foo", "bar");    if (res.code === 1) {      return console.log("被除數必須是除0之外的數");    }   if (res.code === 2) {      return console.log("除數必須是數字");    }    return new Error("不可預知的錯誤");  }

這種函數設計方式和拋出異常的設計方式從功能上說都是一樣的,只是告訴調用方的方式不同。如果你選擇第二種方式,而不是拋出異常,那么實際上需要調用方書寫額外的代碼,用來區分正常情況和異常情況,這并不是一種良好的編程習慣。

然而在 Go 等返回值可以為復數的語言中,我們無需使用上面蹩腳的方式,而是可以:

res, err := divide("foo", "bar");  if err != nil {      log.Fatal(err)  }

這是和 Java 和 JS 等語言使用的 try catch 不一樣的的地方,Go 是通過 panic recover defer 機制來進行異常處理的。感興趣的可以去看看 Go 源碼關于錯誤測試部分

可能大家對 Go 不太熟悉。沒關系,我們來繼續看下 shell。實際上 shell 也是通過返回值來處理異常的,我們可以通過 $? 拿到上一個命令的返回值,這本質上也是一種調用棧的傳播行為,而且是通過返回值而不是捕獲來處理異常的。

作為函數返回值處理和 try catch 一樣,這是語言的設計者和開發者共同決定的一件事情。

上面提到了異常傳播是作用在函數調用棧上的。當一個異常發生的時候,其會沿著函數調用棧逐層返回,直到第一個 catch 語句。當然 catch 語句內部仍然可以觸發異常(自動或者手動)。如果 catch 語句內部發生了異常,也一樣會沿著其函數調用棧繼續執行上述邏輯,專業術語是 stack unwinding。

實際上并不是所有的語言都會進行 stack unwinding,這個我們會在接下來的《運行時異常可以恢復么?》部分講解。

有哪些前端異常處理

偽代碼來描述一下:

function bubble(error, fn) {    if (fn.hasCatchBlock()) {      runCatchCode(error);    }    if (callstack.isNotEmpty()) {      bubble(error, callstack.pop());    }  }

從我的偽代碼可以看出所謂的 stack unwinding 其實就是 callstack.pop()

這就是異常傳播的一切!僅此而已。

異常的處理

我們已經了解來異常的傳播方式了。那么接下來的問題是,我們應該如何在這個傳播過程中處理異常呢?

我們來看一個簡單的例子:

function a() {    b();  }  function b() {    c();  }  function c() {    throw new Error("an error  occured");  }  a();

我們將上面的代碼放到 chrome 中執行, 會在控制臺顯示如下輸出:

有哪些前端異常處理

我們可以清楚地看出函數的調用關系。即錯誤是在 c 中發生的,而 c 是 b 調用的,b 是 a 調用的。這個函數調用棧是為了方便開發者定位問題而存在的。

上面的代碼,我們并沒有 catch 錯誤,因此上面才會有uncaught Error。

那么如果我們 catch ,會發生什么樣的變化呢?catch 的位置會對結果產生什么樣的影響?在 a ,b,c 中 catch 的效果是一樣的么?

我們來分別看下:

function a() {    b();  }  function b() {    c();  }  function c() {    try {      throw new Error("an error  occured");    } catch (err) {      console.log(err);    }  }  a();

(在 c 中 catch)

我們將上面的代碼放到 chrome 中執行, 會在控制臺顯示如下輸出:

有哪些前端異常處理

可以看出,此時已經沒有uncaught Error啦,僅僅在控制臺顯示了標準輸出,而非錯誤輸出(因為我用的是 console.log,而不是 console.error)。然而更重要是的是,如果我們沒有 catch,那么后面的同步代碼將不會執行。

比如在 c 的 throw 下面增加一行代碼,這行代碼是無法被執行的,無論這個錯誤有沒有被捕獲。

function c() {    try {      throw new Error("an error  occured");      console.log("will never run");    } catch (err) {      console.log(err);    }  }

我們將 catch 移動到 b 中試試看。

function a() {    b();  }  function b() {    try {      c();    } catch (err) {      console.log(err);    }  }  function c() {    throw new Error("an error  occured");  }  a();

(在 b 中 catch)

在這個例子中,和上面在 c 中捕獲沒有什么本質不同。其實放到 a 中捕獲也是一樣,這里不再貼代碼了,感興趣的自己試下。

既然處于函數調用棧頂部的函數報錯, 其函數調用棧下方的任意函數都可以進行捕獲,并且效果沒有本質不同。那么問題來了,我到底應該在哪里進行錯誤處理呢?

答案是責任鏈模式。我們先來簡單介紹一下責任鏈模式,不過細節不會在這里展開。

假如 lucifer 要請假。

  •  如果請假天數小于等于 1 天,則主管同意即可

  •  如果請假大于 1 天,但是小于等于三天,則需要 CTO 同意。

  •  如果請假天數大于三天,則需要老板同意。

有哪些前端異常處理

這就是一個典型的責任鏈模式。誰有責任干什么事情是確定的,不要做自己能力范圍之外的事情。比如主管不要去同意大于 1 天的審批。

舉個例子,假設我們的應用有三個異常處理類,它們分別是:用戶輸入錯誤,網絡錯誤 和 類型錯誤。如下代碼,當代碼執行的時候會報錯一個用戶輸入異常。這個異常沒有被 C 捕獲,會 unwind stack 到 b,而 b 中 catch 到這個錯誤之后,通過查看 code 值判斷其可以被處理,于是打印I can handle this。

function a() {    try {      b();    } catch (err) {      if (err.code === "NETWORK_ERROR") {        return console.log("I can handle this");      }      // can't handle, pass it down      throw err;    }  }  function b() {    try {      c();    } catch (err) {      if (err.code === "INPUT_ERROR") {        return console.log("I can handle this");      }      // can't handle, pass it down      throw err;    }  }  function c() {    throw new Error({      code: "INPUT_ERROR",      message: "an error  occured",    });  }  a();

而如果 c 中拋出的是別的異常,比如網絡異常,那么 b 是無法處理的,雖然 b catch 住了,但是由于你無法處理,因此一個好的做法是繼續拋出異常,而不是吞沒異常。不要畏懼錯誤,拋出它。只有沒有被捕獲的異常才是可怕的,如果一個錯誤可以被捕獲并得到正確處理,它就不可怕。

舉個例子:

function a() {    try {      b();    } catch (err) {      if (err.code === "NETWORK_ERROR") {        return console.log("I can handle this");      }      // can't handle, pass it down      throw err;    }  }  function b() {    try {      c();    } catch (err) {      if (err.code === "INPUT_ERROR") {        return console.log("I can handle this");      }    }  }  function c() {    throw new Error({      code: "NETWORK_ERROR",      message: "an error  occured",    });  }  a();

如上代碼不會有任何異常被拋出,它被完全吞沒了,這對我們調試問題簡直是災難。因此切記不要吞沒你不能處理的異常。正確的做法應該是上面講的那種只 catch 你可以處理的異常,而將你不能處理的異常 throw 出來,這就是責任鏈模式的典型應用。

這只是一個簡單的例子,就足以繞半天。實際業務肯定比這個復雜多得多。因此異常處理絕對不是一件容易的事情。

如果說誰來處理是一件困難的事情,那么在異步中決定誰來處理異常就是難上加難,我們來看下。

同步與異步

同步異步一直是前端難以跨越的坎,對于異常處理也是一樣。以 NodeJS 中用的比較多的讀取文件 API 為例。它有兩個版本,一個是異步,一個是同步。同步讀取僅僅應該被用在沒了這個文件無法進行下去的時候。比如讀取一個配置文件。而不應該在比如瀏覽器中讀取用戶磁盤上的一個圖片等,這樣會造成主線程阻塞,導致瀏覽器卡死。

// 異步讀取文件  fs.readFileSync();  // 同步讀取文件  fs.readFile();

當我們試圖同步讀取一個不存在的文件的時候,會拋出以下異常:

fs.readFileSync('something-not-exist.lucifer');  console.log('腦洞前端');  Thrown:  Error: ENOENT: no such file or directory, open 'something-not-exist.lucifer'      at Object.openSync (fs.js:446:3)      at Object.readFileSync (fs.js:348:35) {    errno: -2,    syscall: 'open',    code: 'ENOENT',    path: 'something-not-exist.lucifer'  }

并且腦洞前端是不會被打印出來的。這個比較好理解,我們上面已經解釋過了。

而如果以異步方式的話:

fs.readFile('something-not-exist.lucifer', (err, data) => {if(err) {throw err}});  console.log('lucifer')  lucifer  undefined  Thrown:  [Error: ENOENT: no such file or directory, open 'something-not-exist.lucifer'] {    errno: -2,    code: 'ENOENT',    syscall: 'open',    path: 'something-not-exist.lucifer'  }  >

腦洞前端是會被打印出來的。

其本質在于 fs.readFile 的函數調用已經成功,并從調用棧返回并執行到下一行的console.log('lucifer')。因此錯誤發生的時候,調用棧是空的,這一點可以從上面的錯誤堆棧信息中看出來。

不明白為什么調用棧是空的同學可以看下我之前寫的《一文看懂瀏覽器事件循環》

而 try catch 的作用僅僅是捕獲當前調用棧的錯誤(上面異常傳播部分已經講過了)。因此異步的錯誤是無法捕獲的,比如;

try {    fs.readFile("something-not-exist.lucifer", (err, data) => {      if (err) {        throw err;      }    });  } catch (err) {    console.log("catching an error");  }

上面的 catching an error 不會被打印。因為錯誤拋出的時候, 調用棧中不包含這個 catch 語句,而僅僅在執行fs.readFile的時候才會。

如果我們換成同步讀取文件的例子看看:

try {    fs.readFileSync("something-not-exist.lucifer");  } catch (err) {   console.log("catching an error");  }

上面的代碼會打印 catching an error。因為讀取文件被同步發起,文件返回之前線程會被掛起,當線程恢復執行的時候, fs.readFileSync 仍然在函數調用棧中,因此 fs.readFileSync 產生的異常會冒泡到 catch 語句。

簡單來說就是異步產生的錯誤不能用 try catch 捕獲,而要使用回調捕獲。

可能有人會問了,我見過用 try catch 捕獲異步異常啊。 比如:

rejectIn = (ms) =>    new Promise((_, r) => {      setTimeout(() => {        r(1);      }, ms);    });  async function t() {    try {      await rejectIn(0);    } catch (err) {      console.log("catching an error", err);    }  }  t();

本質上這只是一個語法糖,是 Promise.prototype.catch 的一個語法糖而已。而這一語法糖能夠成立的原因在于其用了 Promise 這種包裝類型。如果你不用包裝類型,比如上面的 fs.readFile 不用 Promise 等包裝類型包裝,打死都不能用 try catch 捕獲。

而如果我們使用 babel 轉義下,會發現 try catch 不見了,變成了 switch case 語句。這就是 try catch “可以捕獲異步異常”的原因,僅此而已,沒有更多。

有哪些前端異常處理

(babel 轉義結果)

我使用的 babel 轉義環境都記錄在這里,大家可以直接點開鏈接查看.

雖然瀏覽器并不像 babel 轉義這般實現,但是至少我們明白了一點。目前的 try catch 的作用機制是無法捕獲異步異常的。

異步的錯誤處理推薦使用容器包裝,比如 Promise。然后使用 catch 進行處理。實際上 Promise 的 catch 和 try catch 的 catch 有很多相似的地方,大家可以類比過去。

和同步處理一樣,很多原則都是通用的。比如異步也不要去吞沒異常。下面的代碼是不好的,因為它吞沒了它不能處理的異常。

p = Promise.reject(1);  p.catch(() => {});

更合適的做法的應該是類似這種:

p = Promise.reject(1);  p.catch((err) => {    if (err == 1) {      return console.log("I can handle this");    }    throw err;  });

徹底消除運行時異常可能么?

我個人對目前前端現狀最為頭疼的一點是:大家過分依賴運行時,而嚴重忽略編譯時。我見過很多程序,你如果不運行,根本不知道程序是怎么走的,每個變量的 shape 是什么。怪不得處處都可以看到 console.log。我相信你一定對此感同身受。也許你就是那個寫出這種代碼的人,也許你是給別人擦屁股的人。為什么會這樣? 就是因為大家太依賴運行時。TS 的出現很大程度上改善了這一點,前提是你用的是 typescript,而不是 anyscript。其實 eslint 以及 stylint 對此也有貢獻,畢竟它們都是靜態分析工具。

我強烈建議將異常保留在編譯時,而不是運行時。不妨極端一點來看:假如所有的異常都在編譯時發生,而一定不會在運行時發生。那么我們是不是就可以信心滿滿地對應用進行重構啦?

幸運的是,我們能夠做到。只不過如果當前語言做不到的話,則需要對現有的語言體系進行改造。這種改造成本真的很大。不僅僅是 API,編程模型也發生了翻天覆地的變化,不然函數式也不會這么多年沒有得到普及了。

不熟悉函數編程的可以看看我之前寫的函數式編程入門篇。

如果才能徹底消除異常呢?在回答這個問題之前,我們先來看下一門號稱沒有運行時異常的語言 elm。elm 是一門可以編譯為 JS 的函數式編程語言,其封裝了諸如網絡 IO 等副作用,是一種聲明式可推導的語言。 有趣的是,elm 也有異常處理。 elm 中關于異常處理(Error Handling)部分有兩個小節的內容,分別是:Maybe 和 Result。elm 之所以沒有運行時異常的一個原因就是它們。 一句話概括“為什么 elm 沒有異常”的話,那就是elm 把異常看作數據(data)。

舉個簡單的例子:

maybeResolveOrNot = (ms) =>    setTimeout(() => {      if (Math.random() > 0.5) {        console.log("ok");      } else {        throw new Error("error");      }    });

上面的代碼有一半的可能報錯。那么在 elm 中就不允許這樣的情況發生。所有的可能發生異常的代碼都會被強制包裝一層容器,這個容器在這里是 Maybe。

有哪些前端異常處理

在其他函數式編程語言名字可能有所不同,但是意義相同。實際上,不僅僅是異常,正常的數據也會被包裝到容器中,你需要通過容器的接口來獲取數據。如果難以理解的話,你可以將其簡單理解為 Promsie(但并不完全等價)。

Maybe 可能返回正常的數據 data,也可能會生成一個錯誤 error。某一個時刻只能是其中一個,并且只有運行的時候,我們才真正知道它是什么。從這一點來看,有點像薛定諤的貓。

有哪些前端異常處理

不過 Maybe 已經完全考慮到異常的存在,一切都在它的掌握之中。所有的異常都能夠在編譯時推導出來。當然要想推導出這些東西,你需要對整個編程模型做一定的封裝會抽象,比如 DOM 就不能直接用了,而是需要一個中間層。

再來看下一個更普遍的例子 NPE:

null.toString();

elm 也不會發生。原因也很簡單,因為 null 也會被包裝起來,當你通過這個包裝類型就行訪問的時候,容器有能力避免這種情況,因此就可以不會發生異常。當然這里有一個很重要的前提就是可推導,而這正是函數式編程語言的特性。這部分內容超出了本文的討論范圍,不再這里說了。

運行時異常可以恢復么?

最后要討論的一個主題是運行時異常是否可以恢復。先來解釋一下,什么是運行時異常的恢復。 還是用上面的例子:

function t() {    console.log("start");    throw 1;    console.log("end");  }  t();

這個我們已經知道了, end 是不會打印的。 盡管你這么寫也是無濟于事:

function t() {    try {      console.log("start");      throw 1;      console.log("end");    } catch (err) {      console.log("relax, I can handle this");    }  }  t();

如果我想讓它打印呢?我想讓程序面對異常可以自己 recover 怎么辦?我已經捕獲這個錯誤, 并且我確信我可以處理,讓流程繼續走下去吧!如果有能力做到這個,這個就是運行時異常恢復。

遺憾地告訴你,據我所知,目前沒有任何一個引擎能夠做到這一點。

這個例子過于簡單, 只能幫助我們理解什么是運行時異常恢復,但是不足以讓我們看出這有什么用?

有哪些前端異常處理

我們來看一個更加復雜的例子,我們這里直接使用上面實現過的函數divide。

function t() {    try {      const res = divide("foo", "bar");      alert(`you got ${res}`);    } catch (err) {      if (err.code === 1) {        return console.log("被除數必須是除0之外的數");      }      if (err.code === 2) {        return console.log("除數必須是數字");      }      throw new Error("不可預知的錯誤");    }  }

如上代碼,會進入 catch ,而不會 alert。因此對于用戶來說, 應用程序是沒有任何響應的。這是不可接受的。

要吐槽一點的是這種事情真的是挺常見的,只不過大家用的不是 alert 罷了。

如果我們的代碼在進入 catch 之后還能夠繼續返回出錯位置繼續執行就好了。

有哪些前端異常處理

如何實現異常中斷的恢復呢?我剛剛說了:據我所知,目前沒有任何一個引擎能夠做到異常恢復。那么我就來發明一個新的語法解決這個問題。

function t() {    try {      const res = divide("foo", "bar");      alert(`you got ${res}`);    } catch (err) {      console.log("releax, I can handle this");      resume - 1;    }  }  t();

上面的 resume 是我定義的一個關鍵字,功能是如果遇到異常,則返回到異常發生的地方,然后給當前發生異常的函數一個返回值 -1,并使得后續代碼能夠正常運行,不受影響。這其實是一種 fallback。

這絕對是一個超前的理念。當然挑戰也非常大,對現有的體系沖擊很大,很多東西都要改。我希望社區可以考慮把這個東西加到標準。

最佳實踐

通過前面的學習,你已經知道了異常是什么,異常是怎么產生的,以及如何正確處理異常(同步和異步)。接下來,我們談一下異常處理的最佳實踐。

我們平時開發一個應用。 如果站在生產者和消費者的角度來看的話。當我們使用別人封裝的框架,庫,模塊,甚至是函數的時候,我們就是消費者。而當我們寫的東西被他人使用的時候,我們就是生產者。

實際上,就算是生產者內部也會有多個模塊構成,多個模塊之間也會有生產者和消費者的再次身份轉化。不過為了簡單起見,本文不考慮這種關系。這里的生產者指的就是給他人使用的功能,是純粹的生產者。

從這個角度出發,來看下異常處理的最佳實踐。

作為消費者

當作為消費者的時候,我們關心的是使用的功能是否會拋出異常,如果是,他們有哪些異常。比如:

import foo from "lucifer";  try {    foo.bar();  } catch (err) {    // 有哪些異常?  }

當然,理論上 foo.bar 可能產生任何異常,而不管它的 API 是這么寫的。但是我們關心的是可預期的異常。因此你一定希望這個時候有一個 API 文檔,詳細列舉了這個 API 可能產生的異常有哪些。

比如這個 foo.bar 4 種可能的異常 分別是 A,B,C 和 D。其中 A 和 B 是我可以處理的,而 C 和 D 是我不能處理的。那么我應該:

import foo from "lucifer";  try {    foo.bar();  } catch (err) {    if (err.code === "A") {      return console.log("A happened");    }    if (err.code === "B") {      return console.log("B happened");    }    throw err;  }

可以看出,不管是 C 和 D,還是 API 中沒有列舉的各種可能異常,我們的做法都是直接拋出。

有哪些前端異常處理

作為生產者

如果你作為生產者,你要做的就是提供上面提到的詳細的 API,告訴消費者你的可能錯誤有哪些。這樣消費者就可以在 catch 中進行相應判斷,處理異常情況。

有哪些前端異常處理

感謝各位的閱讀,以上就是“有哪些前端異常處理”的內容了,經過本文的學習后,相信大家對有哪些前端異常處理這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

施秉县| 高安市| 磴口县| 林州市| 九台市| 托克逊县| 边坝县| 南汇区| 清新县| 连山| 桐乡市| 南阳市| 攀枝花市| 论坛| 巴里| 建始县| 云南省| 丰宁| 山阳县| 汉阴县| 景泰县| 深州市| 和平区| 贺兰县| 布拖县| 信阳市| 海兴县| 治多县| 宁海县| 西盟| 恩平市| 台安县| 广河县| 光泽县| 邯郸县| 广安市| 松原市| 孟村| 聊城市| 古蔺县| 永靖县|