为什么可以尝试捕获异步等待调用?
Why is it possible to try-catch an async-await call?
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 是使用生成器、promises 和协程实现的,如:
// 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
为什么以及如何工作?当 asyncTask
调用之一导致 promise 拒绝时,协程又名 async
如何设法将错误抛出到 try-catch 中?
编辑:正如其他人指出的那样,JavaScript 引擎实现 await
运算符的方式可能与 Babel 和如上所示 coroutine example
。因此,更具体地说:使用本机 JavaScript 是如何工作的?
async
函数
异步函数 returns 由函数主体返回的值解析的承诺,或由主体中抛出的错误拒绝的承诺。
await
运算符 returns 已履行承诺的值,如果等待的承诺被拒绝,则使用拒绝原因抛出错误。
await
抛出的错误可以被 async
函数内的 try-catch 块捕获,而不是让它们向上传播执行堆栈并拒绝通过调用 [=10] 返回的承诺=]函数。
await
运算符还在返回事件循环之前存储执行上下文,以允许 promise 操作继续进行。当内部通知等待承诺的结算时,它会在继续之前恢复执行上下文。
在 async
函数的执行上下文中设置的 try/catch
块不会仅仅因为上下文已被 await
.await
.[=34= 保存和恢复而被更改或变得无效]
顺便说一句
"async-await is implemented using generators, promises, and coroutines"
可能是 Babel 如何转译 async
函数和 await
运算符用法的一部分,但本机实现可以更直接地实现。
生成器函数(更新)
生成器函数的执行上下文存储在其关联的生成器对象的内部 [[Generator Context]] 槽中。 (ECMA 2015 25.3.2)
Yield 表达式将生成器的执行上下文从执行上下文堆栈的顶部移除 (25.3.3.5 of ES6/ECMAScript 2015)
恢复生成器函数会从生成器对象的 [[Generator Context]] 槽恢复函数的执行上下文。
因此,当 yield
表达式 returns.
时,生成器函数有效地恢复了先前的执行上下文
由于正常原因(语法错误、throw
语句、调用抛出的函数)而在生成器函数中抛出的错误可以按预期被 try-catch 块捕获。
通过 Generator.prototype.throw()
抛出错误会在生成器函数中抛出错误,该错误源自最后从生成器函数传递控制权的 yield expression
。与普通错误一样,此错误可以被 try-catch
捕获。 (参考 MDN using throw(), ECMA 2015 25.3.3.4
总结
await
转译代码中使用的 yield
语句周围的 Try-catch 块的工作原因与它们在本机 async
函数中围绕 await
运算符的原因相同 - 它们是在与为拒绝的承诺抛出错误相同的执行上下文中定义。
Why and how does the catch
work? How does the coroutine aka async manage to throw the error inside the try-catch?
一个yield
或await
表达式可以有3种不同的结果:
- 它可以像普通表达式一样求值,得到那个
的结果值
- 它可以像
throw
语句一样求值,导致异常
- 它可以像
return
语句一样计算,导致在结束函数之前只计算 finally
个语句
在挂起的发电机上,这可以通过调用 .next()
, .throw()
or .return()
方法之一来实现。 (当然还有第4种可能的结果,永不恢复)。
…when one of the asyncTask calls results in a promise rejection?
await
ed 值将 Promise.resolve()
d 传递给承诺,然后 .then()
method 通过两个回调在其上调用:当承诺实现时,协同程序恢复正常值(promise 结果),当 promise 被拒绝时,协同程序会突然完成(异常 - 拒绝原因)恢复。
您可以查看 co 库代码或转译器输出 - 字面上 calls gen.throw
from the promise rejection callback。
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 是使用生成器、promises 和协程实现的,如:
// 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
为什么以及如何工作?当 asyncTask
调用之一导致 promise 拒绝时,协程又名 async
如何设法将错误抛出到 try-catch 中?
编辑:正如其他人指出的那样,JavaScript 引擎实现 await
运算符的方式可能与 Babel 和如上所示 coroutine example
。因此,更具体地说:使用本机 JavaScript 是如何工作的?
async
函数
异步函数 returns 由函数主体返回的值解析的承诺,或由主体中抛出的错误拒绝的承诺。
await
运算符 returns 已履行承诺的值,如果等待的承诺被拒绝,则使用拒绝原因抛出错误。
await
抛出的错误可以被 async
函数内的 try-catch 块捕获,而不是让它们向上传播执行堆栈并拒绝通过调用 [=10] 返回的承诺=]函数。
await
运算符还在返回事件循环之前存储执行上下文,以允许 promise 操作继续进行。当内部通知等待承诺的结算时,它会在继续之前恢复执行上下文。
在 async
函数的执行上下文中设置的 try/catch
块不会仅仅因为上下文已被 await
.await
.[=34= 保存和恢复而被更改或变得无效]
顺便说一句
"async-await is implemented using generators, promises, and coroutines"
可能是 Babel 如何转译 async
函数和 await
运算符用法的一部分,但本机实现可以更直接地实现。
生成器函数(更新)
生成器函数的执行上下文存储在其关联的生成器对象的内部 [[Generator Context]] 槽中。 (ECMA 2015 25.3.2)
Yield 表达式将生成器的执行上下文从执行上下文堆栈的顶部移除 (25.3.3.5 of ES6/ECMAScript 2015)
恢复生成器函数会从生成器对象的 [[Generator Context]] 槽恢复函数的执行上下文。
因此,当 yield
表达式 returns.
由于正常原因(语法错误、throw
语句、调用抛出的函数)而在生成器函数中抛出的错误可以按预期被 try-catch 块捕获。
通过 Generator.prototype.throw()
抛出错误会在生成器函数中抛出错误,该错误源自最后从生成器函数传递控制权的 yield expression
。与普通错误一样,此错误可以被 try-catch
捕获。 (参考 MDN using throw(), ECMA 2015 25.3.3.4
总结
await
转译代码中使用的 yield
语句周围的 Try-catch 块的工作原因与它们在本机 async
函数中围绕 await
运算符的原因相同 - 它们是在与为拒绝的承诺抛出错误相同的执行上下文中定义。
Why and how does the
catch
work? How does the coroutine aka async manage to throw the error inside the try-catch?
一个yield
或await
表达式可以有3种不同的结果:
- 它可以像普通表达式一样求值,得到那个 的结果值
- 它可以像
throw
语句一样求值,导致异常 - 它可以像
return
语句一样计算,导致在结束函数之前只计算finally
个语句
在挂起的发电机上,这可以通过调用 .next()
, .throw()
or .return()
方法之一来实现。 (当然还有第4种可能的结果,永不恢复)。
…when one of the asyncTask calls results in a promise rejection?
await
ed 值将 Promise.resolve()
d 传递给承诺,然后 .then()
method 通过两个回调在其上调用:当承诺实现时,协同程序恢复正常值(promise 结果),当 promise 被拒绝时,协同程序会突然完成(异常 - 拒绝原因)恢复。
您可以查看 co 库代码或转译器输出 - 字面上 calls gen.throw
from the promise rejection callback。