使用 async/await 更好地处理错误

2025-05-28

使用 async/await 更好地处理错误

本文旨在提出一种在使用 async/await 语法时处理错误的更好方法。事先了解 Promise 的工作原理非常重要。

从回调地狱到承诺

回调地狱,也称为末日金字塔,是一种反模式,常见于那些不熟悉异步编程的程序员编写的代码中。—— Colin Toh

回调地狱由于回调函数的多次嵌套,使得你的代码向右漂移而不是向下漂移。

我不会详细介绍回调地狱是什么,但我会给出一个例子来说明它的样子。

用户配置文件示例 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

      });
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

承诺

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));

Enter fullscreen mode Exit fullscreen mode

带有承诺的解决方案看起来更清晰、更易读。

使用 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
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

等一下!有问题

如果任何请求中出现承诺拒绝User profile example 3Unhandled 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);
  }
}

Enter fullscreen mode Exit fullscreen mode

问题解决了!

...但错误处理可以改进

您如何知道错误来自哪个异步请求?

我们可以.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);
Enter fullscreen mode Exit fullscreen mode

上面的解决方案可以处理请求中的单个错误,但它混合了多种模式。应该有一种更简洁的方式来使用 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);
}
Enter fullscreen mode Exit fullscreen mode

使用handle效用函数,我们能够避免Unhandled promise rejection错误,并且能够精细地处理错误。

解释

handle实用函数以承诺作为参数并始终解析它,返回一个带有的数组[data|undefined, Error|undefined]

  • 如果传递给函数的承诺handle得到解决,它将返回[data, undefined]
  • 如果被拒绝,该handle函数仍然会解析它并返回[undefined, Error]

类似解决方案

结论

Async/await 具有简洁的语法,但您仍然必须处理异步函数中抛出的异常。

.catch除非您实现自定义错误类,否则处理承诺链中的错误.then可能会很困难。

使用handle效用函数,我们能够避免Unhandled promise rejection错误,并且能够精细地处理错误。

文章来源:https://dev.to/sobiodarlington/better-error-handling-with-async-await-2e5m
PREV
命令行生产力立即提升 100%
NEXT
Polyfill 供应链攻击将恶意软件嵌入 JavaScript CDN 资产中