本篇文章介紹在使用
async
/
await
文法時,一種更好的處理錯誤的方式。在此之前,大家也需要先了解下 Promise 的工作原理。
從回調地獄到 Promise
回調地獄(callback Hell),也稱為“末日金字塔(Pyramid of Doom)”,是在開發者代碼中看到的一種反模式(anti-pattern),這種異步程式設計方式并不明智。- Colin Toh
由于回調函數的嵌套,回調地獄 會使你的代碼向右排布而不是垂直向下排版。
為了更直覺的反映回調函數,這裡舉了一個例子。
使用者資料案例 1
// Code that reads from left to right// instead of top to bottomlet user;let friendsOfUser;getUser(userId, function(data) { user = data; getFriendsOfUser(userId, function(friends) { friendsOfUser = friends; getUsersPosts(userId, function(posts) { showUserProfilePage(user, friendsOfUser, posts, function() { // Do something here }); }); });});
Promise
Promise 是 ES2015(即俗稱的 ES6)引入的一個語言特性,用來更好的處理異步操作,避免回調地獄的出現。
下例中使用 Promise 的
.then
鍊來解決回調地獄問題。
使用者資料案例 2
// A solution with promiseslet user;let friendsOfUser;getUser().then(data => { user = data; return getFriendsOfUser(userId);}).then(friends => { friendsOfUser = friends; return getUsersPosts(userId);}).then(posts => { showUserProfilePage(user, friendsOfUser, posts);}).catch(e => console.log(e));
Promise 的處理方式更加幹淨和可讀。
async/await Promise
async
/
await
是一種特殊的文法,可以用更簡潔的方式處理 Promise。在
funtion
前加
async
關鍵字就能将函數轉換成 Promise。
所有的 async 函數的傳回值都是 Promise。
例子
// Arithmetic addition functionasync function add(a, b) { return a + b;}// Usage:add(1, 3).then(result => console.log(result));// Prints: 4
使用
async
/
await
,可以讓“使用者資料案例 2”看起來更棒。
使用者資料案例 3
async function userProfile() { let user = await getUser(); let friendsOfUser = await getFriendsOfUser(userId); let posts = await getUsersPosts(userId); showUserProfilePage(user, friendsOfUser, posts);}
等等!有個問題
在“使用者資料案例 3”中,如果有一個 Promise reject 了,就會抛出
Unhandled promise rejection
異常。
在此之前寫的代碼都沒有考慮 Promise reject 的情況。未處理的 reject Promise 過去會以靜默的方式失敗,這可能會使調試成為噩夢。
不過現在,Promise reject 時會抛出一個錯誤了。
- Google Chrome 抛出的錯誤:
VM664:1 Uncaught (in promise) Error
- Node 抛出的錯誤則類似這樣:
(node:4796) UnhandledPromiseRejectionWarning: Unhandled promise rejection (r ejection id: 1): Error: spawn cmd ENOENT
[1] (node:4796) 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
。
No promise should be left uncaught(每個 Promise 都要使用 .catch
處理). - Javascript
注意,“使用者資料案例 2”中的
.catch
方法。如果沒有寫
.catch
塊的話,JavaScript 會在 Promise reject 的時候抛出
Unhandled promise rejection
錯誤。
處理“使用者資料案例 3”中的問題比較容易。隻要使用
try...catch
塊包裝下
await
語句就能避免
Unhandled promise rejection
錯誤了。
使用者資料案例 4
async function userProfile() { try { let user = await getUser(); let friendsOfUser = await getFriendsOfUser(userId); let posts = await getUsersPosts(userId); showUserProfilePage(user, friendsOfUser, posts); } catch(e) { console.log(e); }}
問題解決了!
但是錯誤處理還可以再優雅點嗎?
我怎麼知道報錯是來自哪一個異步請求的呢?可以在異步請求上使用
.catch
方法來處理錯誤。
使用者資料案例 5
let user = await getUser().catch(e => console.log('Error: ', e.message));let friendsOfUser = await getFriendsOfUser(userId).catch(e => console.log('Error: ', e.message));let posts = await getUsersPosts(userId).catch(e => console.log('Error: ', e.message));showUserProfilePage(user, friendsOfUser, posts);
上面的解決方案将處理來自請求的單個錯誤,但是會混合使用多種模式。應該有一種更幹淨的方法來使用
async
/
await
而不使用
.catch
方法(嗯,如果你不介意的話,可以這樣做)。
我的更好的 async/await 錯誤處理方案
使用者資料案例 6
/** * @description ### Returns Go / Lua like responses(data, err) * when used with await * * - Example response [ data, undefined ] * - Example response [ undefined, Error ] * * * When used with Promise.all([req1, req2, req3]) * - Example response [ [data1, data2, data3], undefined ] * - Example response [ undefined, Error ] * * * When used with Promise.race([req1, req2, req3]) * - Example response [ data, undefined ] * - Example response [ undefined, Error ] * * @param {Promise} promise * @returns {Promise} [ data, undefined ] * @returns {Promise} [ undefined, Error ] */const handle = (promise) => { return promise .then(data => ([data, undefined])) .catch(error => Promise.resolve([undefined, error]));}async function userProfile() { let [user, userErr] = await handle(getUser()); if(userErr) throw new Error('Could not fetch user details'); let [friendsOfUser, friendErr] = await handle( getFriendsOfUser(userId) ); if(friendErr) throw new Error('Could not fetch user\'s friends'); let [posts, postErr] = await handle(getUsersPosts(userId)); if(postErr) throw new Error('Could not fetch user\'s posts'); showUserProfilePage(user, friendsOfUser, posts);}
這裡使用了一個工具函數
handle
,如此就可以避免
Unhandled promise rejection
報錯,還能細粒度的處理錯誤。
解釋
handle
函數接受一個 Promise 對象作為參數,并總是 resolve 它,以
[data|undefined, Error|undefined]
的形式傳回結果。
- 如果 Promise resolve 了,
函數傳回handle
;[data, undefined]
- 如果 Promise reject 了,
函數傳回handle
。[undefined, Error]
結論
async
/
await
的文法很簡潔,但你還是要處理異步函數裡的抛出的錯誤。除非你實作了自定義錯誤類(custom error classes),否則很難處理
Promise.then
鍊中的
.catch
錯誤處理。使用
handle
工具函數,我們可以避免
Unhandled promise rejection
報錯,還能細粒度的處理錯誤。
(轉載)
作者:zhangbao90s
原文連結:https://juejin.im/post/5e535624518825496e784ccb
END
點選