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.
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.
Share Improve this question edited May 19, 2019 at 6:40 Raju Ahmed asked May 19, 2019 at 6:23 Raju AhmedRaju Ahmed 1807 bronze badges 2- 5 It's because the recursive async call will wait for the current execution to finish before making a new stack frame. Hence you don't run out of stack space. stackoverflow./questions/39459236/… – VLAZ Commented May 19, 2019 at 6:44
-
Yes, your explanation is correct. You would probably have a memory leak if you would
await foo()
though. – Bergi Commented May 19, 2019 at 14:42
2 Answers
Reset to default 5This 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.
Your code doesn't produce stack overflow because when you have call foo
inside function it is not await
ed. 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();