以下函数旨在通过使用await
重复创建微任务来阻止宏任务。
但是为什么它会导致堆栈溢出呢?
我认为await
会将递归调用放在微任务上,从而清空堆栈(显然我错了(。
const waitUsingMicroTasks = async (durationMs, start = performance.now()) => {
const wait = async () => {
let elapsed = (performance.now() - start)
if(elapsed < durationMs) await wait()
}
await wait()
}
waitUsingMicroTasks(1000)
而这一点——我认为几乎是等效的——并没有导致堆叠式气流:
const waitUsingMicroTasks = async (durationMs, start = performance.now()) => {
const wait = async () => {
let elapsed = (performance.now() - start)
if(elapsed < durationMs) Promise.resolve().then(wait)
}
await wait()
}
waitUsingMicroTasks(1000)
这个递归函数完全在同一个微任务中解析。
当遇到await
时,会创建一个新的微任务,这是正确的。但是await
运算符需要有一个Promise或值,以便隐式地将其包装在一个新的Promise中并附加回调。这意味着await
ed的值需要首先进行评估,然后才能安排为微任务。
然而,每次执行wait
都无法在调用下一个wait
之前获得对await
的Promise。因此,实际上堆栈正在同步溢出,而没有调度任何微任务。您永远不会真正获得Promise,因为每个Promise都依赖于下一个要评估的Promise——每个wait()
调用都同步发生在任何await
被解析之前的最后一个调用之后。
您可以通过await
输入一些不需要计算递归调用的值来强制执行微任务:
const waitUsingMicroTasks = async (durationMs, start = performance.now()) => {
const wait = async () => {
let elapsed = await (performance.now() - start)
if(elapsed < durationMs) await wait()
}
await wait()
}
waitUsingMicroTasks(1000)
在第二个例子中,这个问题并不存在,因为您显式地创建了一个Promise,并将wait
作为回调附加到它。这样,wait
不会立即执行(就像await
一样(,但它会在一段时间后运行Promise.resolve((微任务时被调用。
Klaycon的回答几乎涵盖了所有内容,但很容易观察到您对wait
的所有调用都是同步发生的(在堆栈上(:
const waitUsingMicroTasks = async (durationMs, start = performance.now()) => {
let i = 0;
const wait = async () => {
console.log(i);
i += 1;
(i < 20) && (await wait());
}
wait();
console.log('done');
}
waitUsingMicroTasks(5000)
您可以在这里看到,所有的console.log(i)
都发生在console.log('done')
之前,因此wait()
根本不是异步运行的。
正如Klaycon所指出的,等待不是对wait
递归调用的东西似乎可以解决这个问题,但你可以采取的另一种方法是等待实际上异步的东西,比如setTimeout
:
const waitUsingMicroTasks = async (durationMs, start = performance.now()) => {
let i = 0;
console.log('starting', new Date());
while ((performance.now() - start) < durationMs) {
await Promise.resolve();
}
console.log('done', new Date());
}
waitUsingMicroTasks(5000);
console.log('after wait');
函数中没有执行阻塞任务。因此,它将几乎立即执行下一次递归,并且由于没有递归转义,因此会导致堆栈溢出。
在代码中引入像控制台日志这样耗时的任务,它就会运行良好。
let count=0;
const waitUsingMicroTasks = async (durationMs, start = performance.now()) => {
const wait = async () => {
let elapsed = (performance.now() - start);
console.log(++count);
(elapsed < durationMs) && (await wait());
}
await wait();
}
waitUsingMicroTasks(1000)