与异步任务和 Promises 的超时交互


🌐 Timeout interactions with asynchronous tasks and Promises

Promiseasync function 可以让 JavaScript 引擎异步调度任务。默认情况下,这些任务会在当前调用栈上的所有 JavaScript 函数执行完毕后运行。这使得可以绕过 timeoutbreakOnSigint 选项的功能。

例如,以下由 vm.runInNewContext() 执行的代码,在 5 毫秒的超时时间内,会在一个 promise 解决后安排一个无限循环运行。这个被安排的循环永远不会被超时中断:

🌐 For example, the following code executed by vm.runInNewContext() with a timeout of 5 milliseconds schedules an infinite loop to run after a promise resolves. The scheduled loop is never interrupted by the timeout:

const vm = require('node:vm');

function loop() {
  console.log('entering loop');
  while (1) console.log(Date.now());
}

vm.runInNewContext(
  'Promise.resolve().then(() => loop());',
  { loop, console },
  { timeout: 5 },
);
// This is printed *before* 'entering loop' (!)
console.log('done executing'); 

可以通过向创建 Context 的代码传递 microtaskMode: 'afterEvaluate' 来解决这个问题:

🌐 This can be addressed by passing microtaskMode: 'afterEvaluate' to the code that creates the Context:

const vm = require('node:vm');

function loop() {
  while (1) console.log(Date.now());
}

vm.runInNewContext(
  'Promise.resolve().then(() => loop());',
  { loop, console },
  { timeout: 5, microtaskMode: 'afterEvaluate' },
); 

在这种情况下,通过 promise.then() 调度的微任务将在从 vm.runInNewContext() 返回之前运行,并会被 timeout 功能中断。这仅适用于在 vm.Context 中运行的代码,例如 vm.runInThisContext() 不支持此选项。

🌐 In this case, the microtask scheduled through promise.then() will be run before returning from vm.runInNewContext(), and will be interrupted by the timeout functionality. This applies only to code running in a vm.Context, so e.g. vm.runInThisContext() does not take this option.

Promise 回调会进入它们被创建的上下文的微任务队列。例如,如果在上述示例中将 () => loop() 替换为仅 loop,那么 loop 将被推入全局微任务队列,因为它是来自外部(主)上下文的函数,因此也能够逃脱超时。

🌐 Promise callbacks are entered into the microtask queue of the context in which they were created. For example, if () => loop() is replaced with just loop in the above example, then loop will be pushed into the global microtask queue, because it is a function from the outer (main) context, and thus will also be able to escape the timeout.

如果在 vm.Context 中提供异步调度函数,例如 process.nextTick()queueMicrotask()setTimeout()setImmediate() 等,传递给它们的函数将被添加到全局队列中,而这些队列是所有上下文共享的。因此,传递给这些函数的回调也无法通过超时来控制。

🌐 If asynchronous scheduling functions such as process.nextTick(), queueMicrotask(), setTimeout(), setImmediate(), etc. are made available inside a vm.Context, functions passed to them will be added to global queues, which are shared by all contexts. Therefore, callbacks passed to those functions are not controllable through the timeout either.