正确的尝试... 使用异步/等待捕获语法

我喜欢在 Typescript 提供的新的 Async/Await功能的平坦性,等等。但是,我不确定我是否喜欢这样一个事实: 为了以后使用它,我必须在 try...catch块的外部声明变量我是 awaiting。像这样:

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


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

如果我错了,请纠正我,但是将多行业务逻辑放在 try主体中似乎是最佳实践 not,所以我只能选择在块外声明 createdUser,在块中分配它,然后在块后使用它。

在这种情况下,什么是最佳实践?

95032 次浏览

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

如果你只想从承诺中捕捉和处理错误,你有三个选择:

  • 在外部声明变量,并根据是否存在异常进行分支。可以有多种形式,比如

    • catch块中的变量分配一个默认值
    • return early or re-throw an exception from the catch block
    • 设置 catch块是否捕获异常的标志,并在 if条件下测试异常
    • test for the value of the variable to have been assigned
      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 (仍然)没有对 有条件的例外的语法支持。

    如果您的方法没有返回被特定错误拒绝的承诺,您可以通过在 .catch()处理程序中重新抛出一些更合适的内容来实现:

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

    另请参阅 承诺链中的多重挂钩处理以获得此版本的预 async/await版本。

  • Use then有两个回调 instead of try/catch. This really is the least ugly way and my personal recommendation also for its simplicity and correctness, not relying on tagged errors or looks of the result value to distinguish between fulfillment and rejection of the promise:

      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循环或早期的 return

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

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

当读到这里的时候: “等待这个. User.create”。

最后,您可以创建模块“ to.js”,或者简单地使用 等待 JS模块。

您可以在 这篇文章中获得有关 to函数的更多信息

我通常使用的承诺的 catch()函数返回一个对象与 error属性的故障。

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

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


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

如果你不想继续添加 catch()调用,你可以在函数的原型中添加一个 helper 函数:

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

更干净的代码

在承诺捕获处理程序中使用异步/等待。

From what I see, this has been a long-standing problem that has bugged (both meanings) many programmers and their code. The Promise .catch is really no different from try/catch.

Working harmoniously with await/async, ES6 Promise's catch handler provides a proper solution and make code cleaner:

const createUser = await this.User
.create(userInfo)
.catch(error => console.error(error))


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

请注意,虽然这回答了问题,但它吞噬了错误。执行的意图必须是继续执行,而不是抛出。在这种情况下,最好是显式的,并从 catch 返回 false,然后检查用户:

    .catch(error => {
console.error(error);
return false
})


if (!createdUser) // stop operation

很可能有人想扔,所以完整的答案如下:

const createUser = await this.User
.create(userInfo)
.catch(error => {
// do what you need with the error
console.error(error)


// maybe send to Datadog or Sentry


// don't gobble up the error
throw error
})


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


Learning catch doesn't seem like worth it?

上面提到的清洁性好处可能并不明显,但是在现实世界中复杂的异步操作中可以看到。

例如,除了创建用户(this.User.create) ,我们还可以推送通知(this.pushNotification)和发送电子邮件(this.sendEmail)。

这个,用户,创建

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


// collect some fb data and do some background check in parallel
const facebookDetails = await retrieveFacebookAsync(userInfo.email)
.catch(error => {
// we can do some special error handling


// and throw back the error
})
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
}

PushNotification and < strong > this. 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
})

Compose the operations:

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


// business logic here


return await Promise.all([
this.pushNotification(userInfo),
this.sendEmail(userInfo)
]).catch(error => {
// handle errors caused
// by pushNotification or sendEmail
})

No try/catch. And it's clear what errors you are handling.

await this.User.create(userInfo).then(async data => await this.emailService.sendEmail(data.email), async error => await this.sentryService.sendReport(error))