【问题标题】:Correct Try...Catch Syntax Using Async/Await正确 Try...Catch 使用 Async/Await 的语法
【发布时间】:2017-11-23 15:15:14
【问题描述】:

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

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

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

如果我错了,请纠正我,但在 try 正文中放置多行业务逻辑似乎是最佳实践不是,所以我只剩下替代方案了在块外声明createdUser,在块中赋值,之后使用。

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

【问题讨论】:

  • “最佳实践”是使用有效且易于理解、可维护等的方法。我们如何“正确”回答这个问题?我只使用var,知道变量会被提升。那是“错误的”吗?
  • try/catch 应该包含您想要捕获异常的确切内容。如果您要明确查找来自 this.User.create() 的错误,那么您不会在 try/catch 中添加任何其他内容。但是,将一大堆逻辑放在 try 块中也是一个非常合理的设计。这一切都取决于您希望如何/在何处处理错误,以及您希望如何设计异常处理代码以及对给定操作有意义的内容。没有通用的最佳实践。一个通用的最佳实践是确保以某种适当的方式捕获和处理所有错误。
  • async/awaitES2017(今年发布)的一部分,而不是 ES6(两年前发布)。
  • 要添加到@jfriend00 的评论中,如果您将业务逻辑放在try 块中,并且该代码Errors,(TypeErrorReferenceError 等),那将是catched,如果您只希望catch 承诺拒绝,这可能会产生意外行为。

标签: javascript promise async-await try-catch ecmascript-2017


【解决方案1】:

最好不要在 try 正文中放置多行业务逻辑

其实我想说的是。您通常希望 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 的语法。

    如果您的方法没有返回因足够具体的错误而被拒绝的 Promise,您可以通过在 .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。

【讨论】:

  • 您的最后一个示例使用.then() 来解决promise 并提供回调,所以await 可能在那里无效。
  • @dcorking 这是awaiting .then(…) 调用返回的承诺。
  • .then() 调用返回的 lambdas 需要 async 关键字吗?
  • 我看到人们直接将 catch 处理程序附加到 await 。这样做或将其包装在 try/catch 中是个好主意吗?
  • @Saroj const result = await something().catch(err => fallback);let result; try { result = await something(); } catch(err) { result = fallback; } 简单,所以是的,在这种情况下,我认为这是个好主意。
【解决方案2】:

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

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

【讨论】:

  • 我从来没有想过这个,但我试了一下,它有一个有趣的副作用:你可以在.catch() 回调中设置return 的结果来设置值。否则,它返回 undefined。
【解决方案3】:

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

阅读此内容时:“等待 this.User.create”。

最后你可以创建模块“to.js”或者直接使用await-to-js模块。

您可以在this post获取更多关于to函数的信息

【讨论】:

  • then 并不比await 差,因为它更老。它只是不同,适合其他事情。另一方面,这种“await to(…) 样式”让人联想到 nodeback 样式及其所有缺点。
  • 顺便说一句,为了获得更好的性能和简单性,您应该使用promise.then(data => [null, data], err => [err, null]);
  • 正是“它只是不同,适用于其他事物”await 用于创建具有类似“同步”语法的代码,then 的使用和它的回调是更异步的语法.顺便说一句,感谢代码简单性推荐:)
【解决方案4】:

我通常使用 Promise 的 catch() 函数在失败时返回具有 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 对象添加默认的“捕获错误对象”函数,如下所示:

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

【讨论】:

  • 我使用最后一种方法,它给了我'catchToObj' is not a function 错误。
  • @newguy catchToObj 在您调用我的答案中的第一个代码段后,将存在于每个 Promise 对象上。如果你的函数没有返回 Promise 它将不起作用
  • 我正在使用 Sequelize 的 create 方法,它返回一个 Promise&lt;Model&gt;。定义是:public static async create(values: object, options: object): Promise&lt;Model&gt;
【解决方案5】:

更清洁的代码

使用 Promise 捕获处理程序进行异步/等待错误处理。

在我看来,这是一个长期存在的问题,已经困扰了许多程序员和他们的代码(这两种含义)。

ES6 Promise 的 catch 处理程序提供了一个合适的解决方案:

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)。都是异步操作。不需要捕获处理程序。只是异步/等待。

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/catch:

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
    }
}

【讨论】:

    猜你喜欢
    • 2019-03-14
    • 1970-01-01
    • 2021-08-28
    • 2011-06-03
    • 2020-02-20
    • 2018-10-10
    相关资源
    最近更新 更多