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进行工作?
答案 0 :(得分:1)
catch
为什么以及如何工作?协程又名异步如何将错误抛出try-catch中?
yield
或await
表达式可以具有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
运算符中所做的相同原因相同-它们是在与为错误的诺言而抛出错误的执行上下文相同。