使用 Async/Await 正确的 Try...Catch 语法

Correct Try...Catch Syntax Using Async/Await

我喜欢 Typescript 等中可用的新 Async/Await 功能的平坦度。但是,我不确定我是否喜欢我必须声明变量的事实 awaittry...catch 块的外面,以便以后使用它。像这样:

let createdUser
try {
    createdUser = await this.User.create(userInfo)
} catch (error) {
    console.error(error)
}

console.log(createdUser)
// business
// logic
// goes
// here

如果我错了请纠正我,但最好的做法是 而不是 将多行业务逻辑放在 try 正文中,所以我我只剩下在块外声明 createdUser,在块中分配它,然后在之后使用它的替代方法。

这种情况下的最佳做法是什么?

It seems to be best practice not to place multiple lines of business logic in the try body

实际上我会说是。您通常希望 catch 所有 异常都不会使用以下值:

try {
    const createdUser = await this.User.create(userInfo);

    console.log(createdUser)
    // business logic goes here
} catch (error) {
    console.error(error) // from creation or business logic
}

如果你只想从 promise 中捕获和处理错误,你有以下三种选择:

  • 在外面声明变量,根据有无异常进行分支。这可以采用各种形式,例如

    • catch 块中的变量分配默认值
    • return 提早或重新 throw 来自 catch 块的异常
    • 设置一个标志是否catch块捕获异常,并在if条件下测试它
    • 测试已赋值的变量值
      let createdUser; // or use `var` inside the block
      try {
          createdUser = await this.User.create(userInfo);
      } catch (error) {
          console.error(error) // from creation
      }
      if (createdUser) { // user was successfully created
          console.log(createdUser)
          // business logic goes here
      }
    
  • 测试捕获的异常的类型,并据此处理或重新抛出它。

      try {
          const createdUser = await this.User.create(userInfo);
          // user was successfully created
          console.log(createdUser)
          // business logic goes here
      } catch (error) {
          if (error instanceof CreationError) {
              console.error(error) // from creation
          } else {
              throw error;
          }
      }
    

    不幸的是,标准 JavaScript(仍然)没有 conditional exceptions.

    的语法支持

    如果您的方法没有 return promises 因足够具体的错误而被拒绝,您可以通过在 .catch() 处理程序中重新抛出更合适的内容来自己做到这一点:

      try {
          const createdUser = await this.User.create(userInfo).catch(err => {
              throw new CreationError(err.message, {code: "USER_CREATE"});
          });
          …
      } …
    

    另请参阅 Handling multiple catches in promise chain 了解此 async/await 之前的版本。

  • 使用then with two callbacks代替try/catch。这确实是最不丑陋的方式,我个人的建议也是因为它的简单性和正确性,不依赖于标记错误或结果值的外观来区分承诺的履行和拒绝:

      await this.User.create(userInfo).then(createdUser => {
          // user was successfully created
          console.log(createdUser)
          // business logic goes here
      }, error => {
          console.error(error) // from creation
      });
    

    当然它有引入回调函数的缺点,这意味着你不能那么容易地 break/continue 循环或从外部函数做早期的 returns。

另一种更简单的方法是将 .catch 附加到 promise 函数。例如:

const createdUser = await this.User.create(userInfo).catch( error => {
// handle error
})

@Bergi 回答很好,但我认为这不是最好的方法,因为你必须回到旧的 then() 方法,所以我认为更好的方法是在异步函数中捕获错误

async function someAsyncFunction(){
    const createdUser = await this.User.create(userInfo);

    console.log(createdUser)
}

someAsyncFunction().catch(console.log);
  • 但是如果我们在同一个函数中有很多 await 并且需要捕获每个错误怎么办?

您可以声明 to() 函数

function to(promise) {
    return promise.then(data => {
        return [null, data];
    })
    .catch(err => [err]);
}

然后

async function someAsyncFunction(){
    let err, createdUser, anotherUser;

    [err, createdUser] = await to(this.User.create(userInfo));

    if (err) console.log(`Error is ${err}`);
    else console.log(`createdUser is ${createdUser}`);


    [err, anotherUser] = await to(this.User.create(anotherUserInfo));

    if (err) console.log(`Error is ${err}`);
    else console.log(`anotherUser is ${anotherUser}`);
}

someAsyncFunction();

When reading this its: "Wait to this.User.create".

最后您可以创建模块 "to.js" 或者简单地使用 await-to-js 模块。

您可以在 this post

中获得有关 to 函数的更多信息

我通常使用 Promise 的 catch() 函数来 return 一个失败时 error 属性 的对象。

例如,在你的情况下我会这样做:

const createdUser = await this.User.create(userInfo)
          .catch(error => { error }); // <--- the added catch

if (Object(createdUser).error) {
    console.error(error)
}

如果您不想继续添加 catch() 调用,您可以向函数的原型添加一个辅助函数:

Function.prototype.withCatcher = function withCatcher() {
    const result = this.apply(this, arguments);
    if (!Object(result).catch) {
        throw `${this.name}() must return a Promise when using withCatcher()`;
    }
    return result.catch(error => ({ error }));
};

现在您将能够做到:

const createdUser = await this.User.create.withCatcher(userInfo);
if (Object(createdUser).error) {
    console.error(createdUser.error);
}


编辑 03/2020

您还可以向 Promise 对象添加默认 "catch to an error object" 函数,如下所示:

Promise.prototype.catchToObj = function catchToObj() {
    return this.catch(error => ({ error }));
};

然后使用如下:

const createdUser = await this.User.create(userInfo).catchToObj();
if (createdUser && createdUser.error) {
    console.error(createdUser.error);
}

更简洁的代码

async/await 使用 Promise 捕获处理程序进行错误处理。

据我所知,这是一个长期存在的问题,困扰着许多程序员和他们的代码。

ES6 Promise 的 catch handler 提供了一个合适的解决方案:

this.User.create(userInfo).then(createdUser => {
    console.log(createdUser)
    // business
    // logic
    // goes
    // here
}).catch(err => {
    //handle the error
})

但这看起来像是完全删除了 async/await。不对。

想法是使用 Promise 样式并捕获顶级调用者。否则继续使用async/await.

例如,除了创建用户(this.User.create),我们还可以推送通知(this.pushNotification)和发送电子邮件(this.sendEmail)。都是异步操作。不需要捕获处理程序。就 async/await.

this.User.create

this.User.create = async(userInfo) => {

    // collect some fb data and do some background check in parallel
    const facebookDetails = await retrieveFacebookAsync(userInfo.email)
    const backgroundCheck = await backgroundCheckAsync(userInfo.passportID)

    if (backgroundCheck.pass !== true) throw Error('Background check failed')

    // now we can insert everything
    const createdUser = await Database.insert({ ...userInfo, ...facebookDetails })

    return createdUser
}

this.pushNotifcationthis.sendEmail

this.pushNotification = async(userInfo) => {
    const pushed = await PushNotificationProvider.send(userInfo)
    return pushed
})

this.sendEmail = async(userInfo) => {
    const sent = await mail({ to: userInfo.email, message: 'Welcome' })
    return sent
})

当所有异步操作组合在一起时,我们可以使用附有#catch 处理程序的 Promise 样式:

this.User.create(userInfo).then(createdUser => {

    console.log(createdUser)

    // business logic here

    return Promise.all([
        this.pushNotification(userInfo),
        this.sendEmail(userInfo)
    ])
    
}).catch(err => {
    // handle err
})

如果我们用 try/catch 来做到这一点,就必须将所有内容都包装在 try/catch 中(不可取),或者设置许多 try/catches:

var createdUser
try {
    createdUser = await this.User.create(userInfo)
} catch (err) {
    //handle err
}

console.log(createdUser)

// business logic here

if (createdUser) {
    try {
        await this.pushNotification(userInfo)
        await this.sendEmail(userInfo)
    } catch (err) {
        // handle err
    }
}