当 microtaskMode 为 'afterEvaluate' 时,请注意在上下文之间共享 Promises
¥When microtaskMode
is 'afterEvaluate'
, beware sharing Promises between Contexts
在 'afterEvaluate'
模式下,Context
拥有自己的微任务队列,与外部(主)上下文使用的全局微任务队列分开。虽然此模式对于强制执行 timeout
和启用异步任务的 breakOnSigint
至关重要,但它也使得在上下文之间共享 Promise 变得具有挑战性。
¥In 'afterEvaluate'
mode, the Context
has its own microtask queue, separate
from the global microtask queue used by the outer (main) context. While this
mode is necessary to enforce timeout
and enable breakOnSigint
with
asynchronous tasks, it also makes sharing promises between contexts challenging.
在下面的示例中,在内部上下文中创建了一个 Promise,并与外部上下文共享。当外部上下文 await
作用于 Promise 时,外部上下文的执行流会以一种令人惊讶的方式中断:日志语句永远不会执行。
¥In the example below, a promise is created in the inner context and shared with
the outer context. When the outer context await
on the promise, the execution
flow of the outer context is disrupted in a surprising way: the log statement
is never executed.
import * as vm from 'node:vm';
const inner_context = vm.createContext({}, { microtaskMode: 'afterEvaluate' });
// runInContext() returns a Promise created in the inner context.
const inner_promise = vm.runInContext(
'Promise.resolve()',
context,
);
// As part of performing `await`, the JavaScript runtime must enqueue a task
// on the microtask queue of the context where `inner_promise` was created.
// A task is added on the inner microtask queue, but **it will not be run
// automatically**: this task will remain pending indefinitely.
//
// Since the outer microtask queue is empty, execution in the outer module
// falls through, and the log statement below is never executed.
await inner_promise;
console.log('this will NOT be printed');
为了在具有不同微任务队列的上下文之间成功共享 Promise,必须确保每当外部上下文将任务排入内部微任务队列时,内部微任务队列中的任务都会运行。
¥To successfully share promises between contexts with different microtask queues, it is necessary to ensure that tasks on the inner microtask queue will be run whenever the outer context enqueues a task on the inner microtask queue.
每当在使用该上下文的脚本或模块上调用 runInContext()
或 SourceTextModule.evaluate()
时,都会运行给定上下文的微任务队列中的任务。在我们的示例中,可以通过在 await inner_promise
之前安排第二次调用 runInContext()
来恢复正常的执行流程。
¥The tasks on the microtask queue of a given context are run whenever
runInContext()
or SourceTextModule.evaluate()
are invoked on a script or
module using this context. In our example, the normal execution flow can be
restored by scheduling a second call to runInContext()
before await inner_promise
.
// Schedule `runInContext()` to manually drain the inner context microtask
// queue; it will run after the `await` statement below.
setImmediate(() => {
vm.runInContext('', context);
});
await inner_promise;
console.log('OK');
注意:严格来说,在这种模式下,node:vm
与 ECMAScript 规范中关于 入队任务 的描述有所不同,它允许来自不同上下文的异步任务以不同于入队顺序的顺序运行。
¥Note: Strictly speaking, in this mode, node:vm
departs from the letter of
the ECMAScript specification for enqueing jobs, by allowing asynchronous
tasks from different contexts to run in a different order than they were
enqueued.