使用Async / Await正确尝试…捕获语法


72

我喜欢Async/AwaitTypescript等中提供的新功能的平坦性。但是,我不确定我是否必须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在块外声明,在块中分配,以及然后使用它。

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


“最佳实践”是使用有效的,可理解的,可维护的等等。我们如何“正确”回答这个问题?我只是使用var,知道该变量将被吊起。那是“错误”吗?
异端猴子

2
try / catch应该完全包含您要捕获异常的内容。如果您正在寻找错误的根源,this.User.create()那么您就不会在try / catch中放入其他内容。但是,将大量逻辑放入try块中也是一种完全合理的设计。这完全取决于您要如何/在何处处理错误以及如何设计异常处理代码以及对给定操作有意义的内容。没有通用的最佳实践。一种通用的最佳实践是确保以某种适当的方式捕获并处理所有错误。
jfriend00

async/awaitES2017(今年发布)的一部分,而不是ES6(两年前发布)。
Felix Kling

Answers:


57

最好的做法是不要在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早期或重新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(仍然)不支持条件异常的语法。

  • 使用then两个回调,而不是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循环或执行早期操作return


2
您的最后一个示例用于.then()解决Promise并提供回调,因此可能await在那里无效。
dcorking

1
@dcorking正在调用await返回的承诺.then(…)
Bergi

.then()调用返回的lambda是否需要async关键字?
dcorking

2
我见过有人直接把捕获处理程序附加到等待中。这样做或将其包装在try / catch中是一个好主意吗?
Saroj

6
@Sarojconst result = await something().catch(err => fallback);let result; try { result = await something(); } catch(err) { result = fallback; }之简单,是的,在这种情况下,我认为这是一个好主意。
Bergi

11

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

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

1

我通常使用Promisecatch()函数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

在我的答案中调用第一个代码段后,@ newguycatchToObj将存在于每个Promise对象上。如果您的函数未返回a Promise,则将无法正常工作
Arik

我正在使用Sequelize的create方法,该方法返回Promise<Model>。定义是:public static async create(values: object, options: object): Promise<Model>
newguy

0

@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模块。

您可以to这篇文章中获得有关功能的更多信息


2
then不比await因为它老而恶化。只是有所不同,并且适用于其他事物。await to(…)另一方面,这种“样式”让人联想到nodeback样式的所有缺点。
Bergi

顺便说一句,为获得更好的性能和简便性,您应该使用promise.then(data => [null, data], err => [err, null]);
Bergi,

确切地说,“它只是有所不同,并且适用于其他事物”await用于创建具有“同步”之类的语法的代码,使用then和回调是更异步的语法。顺便说一句,感谢您对代码简单性的建议:)
Ivan,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.