Javascript 为什么以下情况会导致堆栈溢出?

Javascript 为什么以下情况会导致堆栈溢出?,javascript,Javascript,以下功能旨在通过使用wait重复创建微任务来阻止宏任务 但为什么它会导致堆栈溢出 我认为await会将递归调用放在一个微任务上,从而清空堆栈(显然我错了) const waitUsingMicroTasks=async(durationMs,start=performance.now())=>{ const wait=async()=>{ 让经过=(performance.now()-start) 如果(经过{ const wait=async()=>{ 让经过=(performance.now

以下功能旨在通过使用
wait
重复创建微任务来阻止宏任务

但为什么它会导致堆栈溢出

我认为
await
会将递归调用放在一个微任务上,从而清空堆栈(显然我错了)

const waitUsingMicroTasks=async(durationMs,start=performance.now())=>{
const wait=async()=>{
让经过=(performance.now()-start)
如果(经过
然而,我认为这几乎是等效的,不会导致堆栈溢出:

const waitUsingMicroTasks=async(durationMs,start=performance.now())=>{
const wait=async()=>{
让经过=(performance.now()-start)
如果(经过waitUsingMicroTasks(1000)
函数中没有执行阻塞任务。因此,它将几乎立即继续执行下一个递归,因为没有递归,所以转义会导致堆栈溢出

在代码中引入一个耗时的任务,如控制台日志,它将运行良好

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)
let count=0;
const waitUsingMicroTasks=async(durationMs,start=performance.now())=>{
const wait=async()=>{
让经过=(performance.now()-start);
console.log(++count);
(经过
此递归函数完全在同一个微任务中解析

当遇到
wait
时,将创建一个新的微任务,您是正确的。但是
await
操作符需要有一个承诺或值,以便隐式地将其包装在新的承诺中并附加回调。这意味着需要先评估正在等待的值,然后才能将其安排为微任务

但是,每次执行
wait
都无法在调用下一个
wait
之前获得
wait
的承诺。因此,实际上堆栈是同步溢出的,没有调度微任务。您永远不会真正获得承诺,因为每个承诺都依赖于要计算的下一个承诺-每个
wait()
调用在解决任何
wait
s之前的最后一个之后同步发生

您可以通过
等待
等待一些不需要计算递归调用的值来强制执行微任务:

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)
const waitUsingMicroTasks=async(durationMs,start=performance.now())=>{
const wait=async()=>{
让经过=等待(performance.now()-start)
如果(经过

在第二个示例中,这个问题不存在,因为您显式创建了一个承诺,并将
wait
作为回调附加到它。通过这种方式,
wait
不会立即执行(就像
wait
),而是在一段时间后运行Promise.resolve()微任务时调用它。

Klaycon的回答几乎涵盖了所有内容,但很容易观察到对
wait
的所有调用都是同步发生的(在堆栈上):

const waitUsingMicroTasks=async(durationMs,start=performance.now())=>{
设i=0;
const wait=async()=>{
控制台日志(i);
i+=1;
(i<20)和(wait wait());
}
等待();
console.log('done');
}

waitUsingMicroTasks(5000)
start和performance.now()之间的差异很可能为0,因此函数将立即连续调用自己。确定。(愚蠢)函数的目的是阻止宏任务。但是微任务是异步的,并且确实提供了“协作多任务”(如果你知道我的意思的话)。这在许多系统上都是一个问题,处理器和你正在尝试做的事情几乎肯定会比时钟的分辨率快,试着用计数器来代替,并进行多次迭代。但是。。。我希望每个间接递归调用都有一个全新的堆栈。所以内存会增加,但我不希望堆栈溢出。好的,谢谢。但这一发现仍然让我困惑。因为
wait
将微任务排队,所以每次堆栈都应该是新的。我不明白引入延迟如何避免堆栈溢出。@52d6c6af引入延迟只会限制在到达
durationMs
之前可以进行的递归调用的数量。这仅仅是因为1000毫秒相对较小。它实际上并没有帮助清除堆栈。而且,这根本不能完成OP试图做的事情,因为它只是在足够的时间过去之前阻塞主线程。在这段时间过去之前,不会安排任务。很好。很好的解释。因此,我如何复制第二个实现,但使用
wait
?@52d6c6af问题的关键是每次执行
wait
都会同步导致另一次执行
wait
。您需要将
wait()
的执行推迟到微任务运行之后,这必然意味着将其设置为回调。我认为唯一可以做到这一点的方法是在调用
wait()
之前先等待其他东西,如我的示例中所示。我发现这似乎是可行的:
if(appeated
…这似乎也可行:
if(appeated
@52d6c6af只是澄清一下:第一个
await
不会立即计算其操作数,而是在代码中实际遇到它时计算,就像大多数其他运算符一样。签出要刷新的页面
await
只是一个一元运算符,当遇到它时,需要在
await