与异步任务和 Promise 的超时交互
Promise
和 async function
可以异步地调度 JavaScript 引擎运行的任务。
默认情况下,这些任务在当前堆栈上的所有 JavaScript 函数执行完毕后运行。
这允许转义 timeout
和 breakOnSigint
选项的功能。
例如,以下代码由 vm.runInNewContext()
执行,超时时间为 5 毫秒,它安排了一个无限循环在 promise 解决后运行。
计划的循环永远不会被超时中断:
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 },
);
// 这是在 'entering loop' 之前打印 (!)
console.log('done executing');
这可以通过将 microtaskMode: 'afterEvaluate'
传给创建 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()
不采用此选项。
Promise 回调被输入到创建它们的上下文的微任务队列中。
例如,如果在上面的例子中 () => loop()
只被 loop
替换,那么 loop
将被推入全局微任务队列,因为它是来自外部(主)上下文的函数,因此也将能够逃脱超时。
如 process.nextTick()
、queueMicrotask()
、setTimeout()
、setImmediate()
等异步调度函数。
在 vm.Context
中可用,传给它们的函数将被添加到全局队列中,由所有上下文共享。
因此,传给这些函数的回调也无法通过超时控制。
Promise
s and async function
s can schedule tasks run by the JavaScript
engine asynchronously. By default, these tasks are run after all JavaScript
functions on the current stack are done executing.
This allows escaping the functionality of the timeout
and
breakOnSigint
options.
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');
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' },
);
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 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.
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.