提问人:MaximPro 提问时间:1/11/2022 最后编辑:BergiMaximPro 更新时间:1/11/2022 访问量:618
异步函数中的额外执行上下文
Extra execution context in async functions
问:
当我阅读规范时,我看到了下一部分:
- 注意:AsyncBlockStart 需要复制执行状态才能恢复其执行。恢复当前 执行上下文。
我不明白这一点。为什么我们需要复制执行上下文?我们不能在没有额外的执行上下文的情况下做到这一点,或者在这种情况下,如果不复制就会破坏什么?
答:
异步函数体的评估发生在一个单独的执行上下文中,该上下文可以重复恢复和挂起。在此上下文中执行的算法步骤在 AsyncBlockStart #3 中给出。
On(在 Await #8 中)和完成(即 /,在 AsyncBlockStart #3.g 中),执行上下文将从堆栈中弹出(如果为 ,则在 Await #9 中暂停以从中断的地方恢复)。
在承诺履行/拒绝时(在 Await #3.c/5.c 中)和启动异步函数(在 AsyncBlockStart #4 中)时,它将被推送到堆栈上并恢复。
这些推送/弹出操作需要对称地相互对应,在启动和恢复代码时,它可能会遇到代码的暂停或结束;在所有四种情况下,堆栈必须在之前和之后具有相同的运行执行上下文。await
return
throw
await
如果从 promise 结算恢复,则正在运行的执行上下文将是当前的 promise 作业。对于 AsyncFunctionStart,该正在运行的执行上下文将是 [[Call]] 期间由 PrepareForOrdinaryCall 步骤创建并推送到异步函数的上下文(该上下文通过 OrdinaryCallEvaluateBody、EvaluateBody 到 EvaluateAsyncFunctionBody,后者创建 promise 并执行 AsyncFunctionStart)。之后,它将从 [[Call]] #7 的堆栈中弹出,就像任何其他函数一样。
那么,为什么我们需要一个额外的执行上下文呢?因为如果我们不创建一个新的(作为当前副本),它就会在 AsyncFunctionStart 结束时弹出,并且 [[Call]] 将无法再次弹出它。(或者更糟糕的是,弹出一个太多)。当然,此问题的另一种解决方案是不复制当前执行上下文,而是重用可挂起的执行上下文,并在 AsyncFunctionStart #4 中的 AsyncBlockStart 之后再次将其推送到堆栈上(不恢复它,仅将其设置为正在运行的执行上下文)。但这会很奇怪,不是吗?
毕竟,无论以哪种方式指定,结果都是一样的。无法从用户代码中观察到执行上下文。
注意:重用相同的执行上下文实际上是生成器所做的。 GeneratorStart #2(从 EvaluateGeneratorBody 调用,其中评估参数声明并创建 Generator 实例)确实使用正在运行的执行上下文作为重复恢复和挂起的上下文。主要区别在于,在生成器的函数调用期间,启动(“第一次恢复”)尚未发生(就像异步函数一样),它只会在第一次调用的后面发生。genContext
next()
实际上,“恢复当前正在执行的上下文定义不明确”在这里并不适用。通过设置“asyncContext
的代码评估状态,以便在恢复评估时 [...]”,当前正在执行的上下文将在 AsyncBlockStart #3 中隐式挂起,就像在 GeneratorStart #4 中发生的那样。
评论