为什么可以尝试捕获async-await呼叫?

时间:2018-09-20 22:34:52

标签: javascript ecmascript-6 error-handling async-await coroutine

JavaScript中有一个常见的反模式:

function handleDataClb(err, data) {
    if(!data) throw new Error('no data found');
    // handle data... 
} 

function f() {
    try {
        fs.readFile('data', 'utf8', handleDataClb);
    } catch(e) {
        // handle error... 
    }
}

f中的该try-catch不会捕获handleDataClb中的错误,因为在稍后的阶段和上下文中不再可见try-catch的情况下调用了回调。

现在JavaScript中的async-await是使用生成器,promise和协程实现的,如下所示:

// coroutine example 
co(function* doTask() {
    try {
        const res1 = yield asyncTask1(); // returns promise
        const res2 = yield asyncTask2(); // returns promise
        return res1 + res2;
    } catch(e) {
        // handle error... 
    }
});

// async-await example
async function doTask() {
    try {
        const res1 = await asyncTask1(); // returns promise
        const res2 = await asyncTask2(); // returns promise
        return res1 + res2;
    } catch(e) {
        // handle error... 
    }
}

这种try-catch的工作方式,通常被称为async-await优于回调的一大优势。

catch为什么以及如何工作?当async之一调用导致promise拒绝时,协程(又名asyncTask)如何设法将错误抛出try-catch中?

编辑:正如其他人指出的那样,JavaScript引擎实现await运算符的方式可能与像Babel这样的编译器使用的纯JavaScript实现非常不同,上面显示为coroutine example。因此,更具体地说:如何使用本机JavaScript进行工作?

2 个答案:

答案 0 :(得分:1)

  

catch为什么以及如何工作?协程又名异步如何将错误抛出try-catch中?

yieldawait表达式可以具有3种不同的结果:

  • 它可以像普通表达式一样求值到该结果的值
  • 它的计算结果类似于throw语句,导致异常
  • 它可以像return语句那样求值,导致仅finally语句在结束函数之前就被求值

在挂起的生成器上,可以通过调用.next().throw().return()方法来实现。 (当然,还有第四种可能的结果,永远都不会恢复。)

  

…当asyncTask调用之一导致承诺被拒绝时?

await的值Promise.resolve()转换为一个承诺,然后通过两个回调对其调用.then() method:当承诺实现时,协程将以常规方式恢复值(承诺结果),并且当承诺被拒绝时,协程将以突然完成的方式恢复(例外-拒绝原因)。

您可以查看co库代码或transpiler输出-从字面上看calls gen.throw from the promise rejection callback

答案 1 :(得分:0)

async功能

一个异步函数返回一个promise,该promise由函数主体返回的值解决,或者被主体中引发的错误拒绝。

如果等待的诺言被拒绝,await运算符使用拒绝原因返回已兑现的诺言的值或引发错误。

await引发的错误可以由async函数内的try-catch块捕获,而不是允许它们沿执行堆栈传播并拒绝通过调用async返回的承诺功能。

await运算符还在返回事件循环之前存储执行上下文,以便允许进行promise操作。内部通知已完成的诺言已解决时,它将在继续执行之前恢复执行上下文。

try/catch函数的执行上下文中设置的async块不会仅仅因为await保存和恢复了上下文而被更改或使其无效。

放在一边

  

“异步生成器使用生成器,promise和协程实现”

可能是Babel如何转换async函数和await运算符用法的一部分,但是本机实现可以更直接地实现。


发电机功能(更新)

生成器函数的执行上下文存储在其关联的生成器对象的内部[[Generator Context]]插槽中。 (ECMA 2015 25.3.2

良种表达式从执行上下文堆栈(25.3.3.5 of ES6/ECMAScript 2015)的顶部删除生成器的执行上下文

恢复生成器函数会从生成器对象的[[Generator Context]]插槽中恢复函数的执行上下文。

因此,当yield表达式返回时,生成器函数可以有效地恢复先前的执行上下文。

由于正常原因,在生成器函数中引发错误(语法错误,throw语句,调用引发的函数)可以被try-catch块按预期捕获。

通过Generator.prototype.throw()引发错误会在生成器函数内引发错误,该错误源自于上次通过生成器函数传递控制权的yield expression。与普通错误一样,try-catch可以捕获此错误。 (引用MDN using throw()ECMA 2015 25.3.3.4

摘要

yield转换代码中使用的await语句周围的try-catch块的工作原理与它们在本机await函数中的async运算符中所做的相同原因相同-它们是在与为错误的诺言而抛出错误的执行上下文相同。