await-to-js 源碼分析,體驗一把捕獲異常的優雅

語言: CN / TW / HK

theme: cyanosis highlight: atelier-dune-light


前言

await-to-js 的源碼較少,代碼較少的情況,可以嘗試採用 3W 方法進行源碼分析,幫助快速瞭解它的實際應用場景。

閲讀本文,會有以下收穫:

  • 瞭解 await-to-js 庫能解決什麼問題。
  • 分析 await-to-js 庫是如何解決問題。
  • 摸索 await-to-js 庫的實際使用場景。

Why:await-to-js 誕生的契機

打開它的 github 地址,有很簡單的一句介紹:

Async await wrapper for easy error handling

這句話的意思是:

Async await 包裝器,便於錯誤處理

等等,asyncawait 怎麼捕獲異常來着。

常規異常捕獲

一般情況下,await 命令後面是一個 Promise 對象,返回該對象的結果。

如果我們希望捕獲 Promise 中的錯誤, 這個時候需要將 await 放在try...catch結構裏面。

同時,這樣做,無論前面的 await 是否成功,都不會影響後續的功能。

功能設計

該功能設計源自源碼的 examples 文件中提供的案例,我將它改成了使用 await-to-js 前的寫法。

  • UserModel:提供了 findById 方法,是一個 Promise,它主要做了兩件事,如果 userId 存在,則返回 userObjet 對象,如果 userId 不存在,則用 reject 拋出錯誤。
  • asyncTask: 兩個參數,userId 和結果返回函數。使用async / await 處理異步操作 UserModel,將 await 放在 try...catch 結構裏面。如果 UserModel 正常,則判斷是否查到了 userId,如果 UserModel 阻塞,也不影響後面代碼的執行。
  • 調用 asyncTask 的時候,第一個參數傳 null 。

``` const UserModel = { findById: userId => { return new Promise((resolve, reject) => { if (userId) { const userObjet = { id: userId, notificationsEnabled: true, };

    return resolve(userObjet);
  }

  reject('Data is missing');
});

}, };

/* * 異步任務 / async function asyncTask(userId, cb) { try { const user = await UserModel.findById(userId); if (!(user && user.id)) return cb('No user found'); } catch () {; } return cb('前面的 await 可能失敗了'); }

asyncTask(null, (err, newTask) => { console.log('fail'); console.log(err); console.log(newTask); }); ```

運行結果

因為 userId 的值為 null,所以 UserModel.findById 拋出了異常,而我在 catch 中沒有做任何處理,所以後面的代碼正常運行,將最後的代碼處理結果拋給了 cb 函數。

所以,最終打印 cb 裏的結果:

捕獲異常

如果需要進行異常的捕獲,在 catch 中將錯誤返回即可。

async function asyncTask(userId, cb) { try { const user = await UserModel.findById(userId); if (!(user && user.id)) return cb('No user found'); } catch (e) { return cb(e); } return cb('前面的 await 可能失敗了'); }

運行結果

此時返回的報錯內容為: Data is missing

契機

上面的例子中,雖然代碼運行正常,也可以進行異常的捕獲。

但是有個問題,如果想捕獲不同場景下的異常,需要將每個場景都放到 try...catch 結構中,這樣代碼會顯得有些宂餘。

能否,將 try...catch 結構封裝起來,直接返回結果?

這不,await-to-js 就誕生了麼。

What:如何優雅捕獲異常

開頭説源碼內容少,並不是説説看,它是真的少。

核心源碼

源碼用的 TypeScript,因為代碼量不多,所以沒有用過 TypeScript 的開發者,也可以很順暢的閲讀。

``` /* * @param { Promise } promise * @param { Object } errorExt - Additional Information you can pass to the err object * @return { Promise } / export function to ( promise: Promise, errorExt?: object ): Promise<[U, undefined] | [null, T]> { return promise .then<[null, T]>((data: T) => [null, data]) .catch<[U, undefined]>((err: U) => { if (errorExt) { const parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; }

  return [err, undefined];
});

}

export default to; ```

不過,想看用 JavaScript 寫的也不難,將項目 build 一下,就能得到。順便在上面加了些註釋:

``` /* * @param { Promise } promise * @param { Object } errorExt - Additional Information you can pass to the err object * @return { Promise } / function to(promise, errorExt) { return promise .then(function (data) { // 返回數組值:第一個元素是錯誤信息(只有報錯時有值),第二個元素是數據信息(只有成功時有值) return [null, data]; }).catch(function (err) { // =>true: 如果添加了額外的錯誤描述,則和錯誤描述合到一個空對象上 if (errorExt) { var parsedError = Object.assign({}, err, errorExt); return [parsedError, undefined]; } return [err, undefined]; }); }

export { to }; export default to; ```

這裏需要注意下面幾點:

  • errorExtObject 類型。
  • Object.assign 在進行拷貝的時候,會把非 Object 類型轉成 Object 類型。

``` const err1 = 'time'; const err2 = 's';

const task = Object.assign({}, err1, err2); console.log(task); // { '0': 's', '1': 'i', '2': 'm', '3': 'e' } ```

疑問

關於 errorExt 我是有疑問的,它的結果到底怎麼回顯?

源碼中,promise 中的 reject 拋出的都是字符串。上面提到了,Object.assign 會將字符串轉成 Object 類型,得到的結果不是很直觀。

來個例子看看運行結果:

``` async function asyncTask(userId, cb) { let err, user, savedTask, notification; [err, user] = await to(UserModel.findById(userId), { errorExt: ' ext error' }); if (err) return cb(err);

cb(null, savedTask); } asyncTask(null, (err, newTask) => { console.log('fail'); console.log(err); console.log(newTask); }); ```

運行結果

這個結果並不直觀

想要更直觀的內容,只能將結果進行一次特殊處理,比如對象循環,只展示 value 值

if (err) { let errList = Object.values(err); let errStr = errList.join(''); return cb(errStr); }

運行結果

這次打印的內容直觀多了。

關於疑問的部分,僅個人想法,歡迎留言討論

示例演示

還是上面的例子,這次使用 await-to-js 捕獲異常。只需要一行代碼,便可輕鬆捕獲異常。

``` import to from '../../dist';

async function asyncTask(userId, cb) { let err, user, savedTask, notification; [err, user] = await to(UserModel.findById(userId)); // 直接捕獲異常 if (err) return cb(err); }

asyncTask(null, (err, newTask) => { console.log('fail'); console.log(err); console.log(newTask); }); ```

運行結果

How:使用場景小結

不僅可以直接捕獲異常,還可以根據數據的值進行特殊的邏輯處理,幫助完成一些複雜邏輯。

``` async function asyncTask(userId, cb) { let err, user, savedTask, notification; [err, user] = await to(UserModel.findById(userId)); if (!(user && user.id)) return cb('No user found');

cb(null, savedTask); } ```

運行結果

在上面的代碼中,根據得到的 user 的值,進行判斷,在實際開發中的實用性更強一些。

總結

await-to-js 源碼讀完,個人感覺實際用起來,代碼會寫的比較優雅。而且它的源碼內容不多,簡單易讀。

簡單總結一下本文收穫:

1、使用 await-to-js,可以幫助改善異步捕獲異常的宂餘寫法。

2、源碼的代碼量不多,可以作為工具類放到項目中使用。

3、在日常開發中,可以留意一下類似的功能改造,減少重複寫法。

以上就是本次分享的內容。如果覺得有幫助,歡迎留言討論、點贊 、收藏,持續產出技術分享。


我是 葉一一,非職業「傳道授業解惑」的技術博主。「趣學前端」、「CSS暢想」系列作者。

華夏美食、國漫、古風重度愛好者,刑偵、無限流小説初級玩家。

歡迎技術或非技術問題的討論。

本文正在參加「金石計劃」