使用 async/await 更好地处理错误
本文旨在提出一种在使用 async/await 语法时处理错误的更好方法。事先了解 Promise 的工作原理非常重要。
从回调地狱到承诺
回调地狱由于回调函数的多次嵌套,使得你的代码向右漂移而不是向下漂移。
我不会详细介绍回调地狱是什么,但我会给出一个例子来说明它的样子。
用户配置文件示例 1
// Code that reads from left to right
// instead of top to bottom
let 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
});
});
});
});
承诺
Promises被引入到 Javascript(ES6)语言中,以便更好地处理异步操作,而不会变成回调地狱。
下面的示例使用承诺通过使用多个链式.then
调用而不是嵌套回调来解决回调地狱。
用户配置文件示例 2
// A solution with promises
let 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));
带有承诺的解决方案看起来更清晰、更易读。
使用 async/await 的承诺
Async/await是一种特殊的语法,可以更简洁地处理 Promise。
在 any 之前添加 asyncfunction
可以将函数转换为 Promise。
所有
async
函数都返回承诺。
例子
// Arithmetic addition function
async function add(a, b) {
return a + b;
}
// Usage:
add(1, 3).then(result => console.log(result));
// Prints: 4
User profile example 2
使用 async/await使外观更加美观
用户配置文件示例 3
async function userProfile() {
let user = await getUser();
let friendsOfUser = await getFriendsOfUser(userId);
let posts = await getUsersPosts(userId);
showUserProfilePage(user, friendsOfUser, posts);
}
等一下!有问题
如果任何请求中出现承诺拒绝User profile example 3
,Unhandled promise rejection
则会抛出异常。
在此之前,Promise 的拒绝不会抛出错误。之前,未处理拒绝的 Promise 会默默地失败,这会让调试变成一场噩梦。
谢天谢地,承诺现在在被拒绝时会被抛出。
-
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.
任何承诺都不应被忽视。 - Javascript
注意.catch
中的方法User profile example 2
。
如果没有 .catch 块,Unhandled promise rejection
当 Promise 被拒绝时,JavaScript 将会抛出错误。
解决这个问题User profile example 3
很容易。Unhandled promise rejection
可以通过将 await 操作包装在 try...catch 块中来防止错误:
用户配置文件示例 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
方法(好吧,如果你不介意的话,你可以这样做)。
以下是我更好的异步/等待错误处理的解决方案
用户配置文件示例 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
实用函数以承诺作为参数并始终解析它,返回一个带有的数组[data|undefined, Error|undefined]
。
- 如果传递给函数的承诺
handle
得到解决,它将返回[data, undefined]
; - 如果被拒绝,该
handle
函数仍然会解析它并返回[undefined, Error]
类似解决方案
结论
Async/await 具有简洁的语法,但您仍然必须处理异步函数中抛出的异常。
.catch
除非您实现自定义错误类,否则处理承诺链中的错误.then
可能会很困难。
使用handle
效用函数,我们能够避免Unhandled promise rejection
错误,并且能够精细地处理错误。