以下功能旨在通过使用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)
答案 0 :(得分:3)
此递归功能完全在同一个微任务中解决。
是正确的,当遇到await
时会创建一个新的微任务。但是await
运算符需要有一个Promise或值,以便将其隐式包装在新的Promise中并附加回调。这意味着await
编辑的值需要首先进行评估,然后才能将其安排为微任务。
但是,每次执行wait
都无法在调用下一个await
之前获得对wait
的承诺。因此,实际上堆栈是在没有调度微任务的情况下同步溢出的。您实际上从未获得过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()微任务运行一段时间后被调用。
答案 1 :(得分:1)
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');
答案 2 :(得分:0)
该功能没有完成阻止任务。因此,它将几乎立即继续执行下一个递归,并且由于没有递归转义,因此不会导致堆栈溢出。
在代码中引入诸如控制台日志之类的耗时任务,它将运行良好。
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)