Why doesn't an infinitely recursive async function cause stack overflow?

那年仲夏 提交于 2021-02-06 16:28:44

问题


I was thinking what happens when an async function recursively calls itself infinitely. My thought was that it will not cause stack overflow. But I can't exactly point out why this is the case.

const foo = async () => {
    const txt = await Promise.resolve("foo");
    console.log(txt);
    foo();
}

foo();

The code above prints "foo" infinitely without overflowing the stack.

My idea is that the code is conceptually similar to the following, it doesn't cause stack overflow because the recursive call to foo() is inside the callback, the original call to foo() will return before that.

const bar = () => {
    console.log("foo");
    foo();
}

const foo = () => {
    setImmediate(bar);
}

foo();

I am looking for the exact answer as to what happens in the case of the async function.


回答1:


Your code doesn't produce stack overflow because when you have call foo inside function it is not awaited. If you write await foo(); then it should cause stack overflow.

Consider below two cases:

Case 1 Here as per your code. From a() it will call foo without await. So what will happen when it calls foo() as it is async function, it would be scheduled to run after the current execution resolves. Or even more precisely, it will be queued for later execution and immediately a() will also continue from next line. You can see the output that a() is getting ended first, it doesn't wait for call stack for foo to return;

const foo = async () => {
    const txt = await Promise.resolve("foo");
    console.log(txt);
}

const a = async () => {
    const txt = await Promise.resolve("a");
    console.log(txt);
    foo();
    console.log("-- ENd of a() --");
}

a();

Case 2 Here inside a() it will call foo with await. You can see the output that a() is waiting for return from foo() then only it will continue on next line.

const foo = async () => {
    const txt = await Promise.resolve("foo");
    console.log(txt);
}

const a = async () => {
    const txt = await Promise.resolve("a");
    console.log(txt);
    await foo();
    console.log("-- ENd of a() --");
}

a();



回答2:


This function is syntactic sugar for

const foo = () => 
  Promise.resolve(
    Promise.resolve("foo")
    .then(txt => {
      console.log(txt);
      foo();
    })
  );

foo();

This itself can be rewritten with less dependencies as

const foo = () =>
  queueMicrotask(() =>
    queueMicrotask(() => {
      console.log("foo");
      foo();
    })
  );
foo();

Window.queueMicrotask is a quite new method that gives us a way to trigger the queue a microtask operation that Promise.resolve and hence await do trigger.
Basically, this operation pushes the microtask at the end of the current execution, but before the end of the current event loop.

The sixth point of the algorithm reads

Set task's script evaluation environment settings object set to an empty set.

This is why you don't have a stack-overflow here. However, since you are never exiting the event loop, you are blocking the browser.



来源:https://stackoverflow.com/questions/56205298/why-doesnt-an-infinitely-recursive-async-function-cause-stack-overflow

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!