怎樣針對JavaScript中的非同步函式進行異常處理及測試
翻譯:瘋狂的技術宅
原文:https://www.valentinog.com/bl...
本文首發微信公眾號:jingchengyideng
歡迎關注,每天都給你推送新鮮的前端技術文章
可以在 Javascript 的非同步函式中丟擲錯誤嗎?
這個話題已被反覆提起過幾百次,不過這次讓我們從TDD(Test-Driven Development) 的角度來回答它。
如果你能不在Stackoverflow上搜索就能回答這個問題,會給我留下深刻的印象。
如果不能的話也可以很酷。 繼續往下讀,你就能學到!
你將學到什麼
通過後面的內容你將學到:
- 如何從 Javascript 的非同步函式中丟擲錯誤
- 如何使用 Jest 測試來自非同步函式的異常
要求
要繼續往下讀你應該:
- 對 Javascript 和 ES6 有基本的瞭解
- 安裝 Node.Js 和 Jest
如何從 Javascript 的常規函式中丟擲錯誤
使用異常而不是返回碼(清潔程式碼)。
丟擲錯誤是處理未知的最佳方法。
同樣的規則適用於各種現代語言:Java、Javascript、Python、Ruby。
你可以從函式中丟擲錯誤,可以參照以下示例:
function upperCase(name) { if (typeof name !== "string") { throw TypeError("name must be a string"); } return name.toUpperCase(); } module.exports = upperCase;
這是對它的測試(使用Jest):
"use strict"; const assert = require("assert"); const upperCase = require("../function"); describe("upperCase function", () => { test("it throws when name is not provided", () => { assert.throws(() => upperCase()); }); test("it throws when name is not a string", () => { assert.throws(() => upperCase(9)); }); });
也可以從 ES6 的類中丟擲錯誤。在 Javascript 中編寫類時,我總是在建構函式中輸入意外值。下面是一個例子:
class Person { constructor(name) { if (typeof name !== "string") { throw TypeError("name must be a string"); } this.name = name; } // some method here } module.exports = Person;
以下是該類的測試:
"use strict"; const assert = require("assert"); const Person = require("../index"); describe("Person class", () => { test("it throws when name is not provided", () => { assert.throws(() => new Person()); }); test("it throws when name is not a string", () => { assert.throws(() => new Person(9)); }); });
測試確實通過了:
PASStest/index.test.js Person class ✓ it throws when name is not provided (1ms) ✓ it throws when name is not a string
安排的明明白白!
所以無論異常是從常規函式還是從類建構函式(或從方法)丟擲的,一切都會按照預期工作。
但是如果我想從非同步函式中丟擲錯誤 怎麼辦?
我可以在測試中使用assert.throws
嗎?
各位看官請上眼!
測試異常
既然都看到這裡了,所以你應該知道什麼是 Javascript 的非同步函式,對嗎?先看一段程式碼:
class Person { constructor(name) { if (typeof name !== "string") { throw TypeError("name must be a string"); } this.name = name; } // some method here } module.exports = Person;
假設你要新增非同步方法來獲取有關該人的資料。這種方法需要一個網址。如果url不是字串,就要像上一個例子中那樣丟擲錯誤。
先來修改一下這個類:
class Person { constructor(name) { if (typeof name !== "string") { throw TypeError("name must be a string"); } this.name = name; } async getData(url) { if (typeof url !== "string") { throw TypeError("url must be a string"); } // const response = await fetch(url) // do stuff } } module.exports = Person;
如果我執行程式碼會怎麼樣?試試吧:
const Person = require("../index"); const valentinogagliardi = new Person("valentinogagliardi"); valentinogagliardi.getData();
結果是這樣
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: name must be a string DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
果然不出所料,非同步方法返回了一個Promise rejection ,從嚴格意義上來講,並沒有丟擲什麼東西。錯誤被包含在了Promise rejection中。
換句話說,我
不能使用assert.throws
來測試它。
讓我們通過測試來驗證一下:
"use strict"; const assert = require("assert"); const Person = require("../index"); describe("Person methods", () => { test("it throws when url is not a string", () => { const valentinogagliardi = new Person("valentinogagliardi"); assert.throws(() => valentinogagliardi.getData()); }); });
測試失敗了!
FAILtest/index.test.js Person methods › it throws when url is not a string assert.throws(function) Expected the function to throw an error. But it didn't throw anything. Message: Missing expected exception.
有沒有悟出點什麼?
看把你能的,來抓我啊
從嚴格意義上講非同步函式和非同步方法不會丟擲錯誤。非同步函式和非同步方法總是返回一個Promise,無論它已完成還是被拒絕 ,你必須附上then() 和 catch() ,無論如何。(或者將方法包裝在try/catch中)。被拒絕的Promise將會在堆疊中傳播,除非你抓住(catch)它 。
至於測試程式碼,應該這樣寫:
"use strict"; const assert = require("assert"); const Person = require("../index"); describe("Person methods", () => { test("it rejects when url is not a string", async () => { expect.assertions(1); const valentinogagliardi = new Person("valentinogagliardi"); await expect(valentinogagliardi.getData()).rejects.toEqual( TypeError("url must be a string") ); }); });
我們測試的不能是普通的異常,而是帶有TypeError的rejects。
現在測試通過了:
PASStest/index.test.js Person methods ✓ it rejects when url is not a string
那程式碼該怎麼寫呢?為了能夠捕獲錯誤,你應該這樣重構:
const Person = require("../index"); const valentinogagliardi = new Person("valentinogagliardi"); valentinogagliardi .getData() .then(res => res) .catch(err => console.error(err));
現在異常將會出現在控制檯中:
TypeError: url must be a string at Person.getData (/home/valentino/Documenti/articles-and-broadcasts/throw-from-async-functions-2018-04-02/index.js:12:13) at Object.<anonymous> (/home/valentino/Documenti/articles-and-broadcasts/throw-from-async-functions-2018-04-02/index.js:22:4) // ...
如果你想要更多的try/catch.,有一件重要的事需要注意。
下面的程式碼不會捕獲錯誤:
const Person = require("../index"); async function whatever() { try { const valentinogagliardi = new Person("valentinogagliardi"); await valentinogagliardi.getData(); // do stuff with the eventual result and return something } catch (error) { throw Error(error); } } whatever();
記住:被拒絕的Promise會在堆疊中傳播,除非你抓住(catch) 它。
要在 try/catch 中正確捕獲錯誤,可以像這樣重構:
async function whatever() { try { const valentinogagliardi = new Person("valentinogagliardi"); await valentinogagliardi.getData(); // do stuff with the eventual result and return something } catch (error) { throw Error(error); } } whatever().catch(err => console.error(err));
這就是它的工作原理。
總結
最後總結一下:
從非同步函式丟擲的錯誤不會是“普通的異常” 。
非同步函式和非同步方法總是返回一個Promise,無論是已解決還是被拒絕。
要攔截非同步函式中的異常,必須使用catch() 。
以下是在Jest中測試異常的規則:
-
使用
assert.throws
來測試普通函式和方法中的異常 -
使用
expect
+rejects
來測試非同步函式和非同步方法中的異常
如果你對如何使用 Jest 測試 Koa 2 感興趣,請檢視使用Jest和Supertest進行測試的簡紹 這篇文章。
感謝閱讀!
本文首發微信公眾號:jingchengyideng
歡迎關注,每天都給你推送新鮮的前端技術文章