异步函数中的额外执行上下文
Extra execution context in async functions
当我阅读规范时,我看到了下一个 part:
- NOTE: Copying the execution state is required for AsyncBlockStart to resume its execution. It is ill-defined to resume a currently
executing context.
我不明白这个。为什么我们需要复制执行上下文?我们不能在没有额外执行上下文的情况下做到这一点,否则在这种情况下如果不复制会破坏什么?
异步函数体的计算发生在一个单独的执行上下文中,可以重复恢复和暂停。在此上下文中执行的算法步骤在 AsyncBlockStart #3.
中给出
在 await
上(在 Await #8) and completion (i.e. return
/throw
, in AsyncBlockStart #3.g), the execution context is popped off the stack (and in case of await
, suspended to resume where it left off, in Await #9 中)。
根据承诺 fulfillment/rejection(在 Await #3.c/5.c) and when starting the async function (in AsyncBlockStart #4 中),它被推入堆栈并恢复。
这些 push/pop 操作需要相互对称对应,无论是开始还是恢复代码都可能 运行 进入代码的暂停或结束;并且在所有四种情况下,堆栈必须在顶部之前和之后具有相同的 运行ning 执行上下文。
如果从 promise 结算中恢复,运行ning 执行上下文将是当前的 promise 作业。如果 AsyncFunctionStart, that running execution context will be the one created and pushed by the PrepareForOrdinaryCall steps during the [[Call]] to the async function (which goes through OrdinaryCallEvaluateBody, EvaluateBody to EvaluateAsyncFunctionBody which creates the promise and performs AsyncFunctionStart). It will afterwards be popped from the stack in [[Call]] #7 类似于任何其他函数。
那么为什么我们需要一个额外的执行上下文呢?因为如果我们不创建一个新的(作为当前的副本),它会在 AsyncFunctionStart 结束时已经弹出,并且 [[Call] ] 将无法再次弹出它。 (或者更糟的是,弹出太多)。当然,这个问题的另一种解决方案是不复制当前执行上下文,而是重用可暂停执行上下文,然后将其再次压入堆栈(不恢复它,仅将其设置为 运行ning 执行上下文)在 AsyncFunctionStart #4 中的 AsyncBlockStart 之后。但这会很奇怪,不是吗?
毕竟,无论以何种方式指定,结果都是一样的。从用户代码中无法观察到执行上下文。
注意:重复使用相同的执行上下文实际上是生成器所做的。 GeneratorStart #2 (which is called from EvaluateGeneratorBody,其中评估参数声明并创建 Generator 实例)确实使用 运行ning 执行上下文作为重复恢复和暂停的 genContext
。主要区别在于启动(“第一次恢复”)在生成器的函数调用期间还没有发生(因为它确实发生在异步函数中),它只会在第一个 next()
调用的后期发生。
实际上“恢复当前正在执行的上下文是错误的。”在这里不适用。当前执行的上下文将通过设置“ asyncContext
的代码评估状态在 AsyncBlockStart #3 中隐式暂停,这样当评估恢复时 [...]”,就像它确实发生在 GeneratorStart #4.
当我阅读规范时,我看到了下一个 part:
- NOTE: Copying the execution state is required for AsyncBlockStart to resume its execution. It is ill-defined to resume a currently executing context.
我不明白这个。为什么我们需要复制执行上下文?我们不能在没有额外执行上下文的情况下做到这一点,否则在这种情况下如果不复制会破坏什么?
异步函数体的计算发生在一个单独的执行上下文中,可以重复恢复和暂停。在此上下文中执行的算法步骤在 AsyncBlockStart #3.
中给出
在 await
上(在 Await #8) and completion (i.e. return
/throw
, in AsyncBlockStart #3.g), the execution context is popped off the stack (and in case of await
, suspended to resume where it left off, in Await #9 中)。
根据承诺 fulfillment/rejection(在 Await #3.c/5.c) and when starting the async function (in AsyncBlockStart #4 中),它被推入堆栈并恢复。
这些 push/pop 操作需要相互对称对应,无论是开始还是恢复代码都可能 运行 进入代码的暂停或结束;并且在所有四种情况下,堆栈必须在顶部之前和之后具有相同的 运行ning 执行上下文。
如果从 promise 结算中恢复,运行ning 执行上下文将是当前的 promise 作业。如果 AsyncFunctionStart, that running execution context will be the one created and pushed by the PrepareForOrdinaryCall steps during the [[Call]] to the async function (which goes through OrdinaryCallEvaluateBody, EvaluateBody to EvaluateAsyncFunctionBody which creates the promise and performs AsyncFunctionStart). It will afterwards be popped from the stack in [[Call]] #7 类似于任何其他函数。
那么为什么我们需要一个额外的执行上下文呢?因为如果我们不创建一个新的(作为当前的副本),它会在 AsyncFunctionStart 结束时已经弹出,并且 [[Call] ] 将无法再次弹出它。 (或者更糟的是,弹出太多)。当然,这个问题的另一种解决方案是不复制当前执行上下文,而是重用可暂停执行上下文,然后将其再次压入堆栈(不恢复它,仅将其设置为 运行ning 执行上下文)在 AsyncFunctionStart #4 中的 AsyncBlockStart 之后。但这会很奇怪,不是吗?
毕竟,无论以何种方式指定,结果都是一样的。从用户代码中无法观察到执行上下文。
注意:重复使用相同的执行上下文实际上是生成器所做的。 GeneratorStart #2 (which is called from EvaluateGeneratorBody,其中评估参数声明并创建 Generator 实例)确实使用 运行ning 执行上下文作为重复恢复和暂停的 genContext
。主要区别在于启动(“第一次恢复”)在生成器的函数调用期间还没有发生(因为它确实发生在异步函数中),它只会在第一个 next()
调用的后期发生。
实际上“恢复当前正在执行的上下文是错误的。”在这里不适用。当前执行的上下文将通过设置“ asyncContext
的代码评估状态在 AsyncBlockStart #3 中隐式暂停,这样当评估恢复时 [...]”,就像它确实发生在 GeneratorStart #4.