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.
The most-common cause of stack overflow is excessively deep or infinite recursion, in which a function calls itself so many times that the space needed to store the variables and information associated with each call is more than can fit on the stack.
A general method for avoiding a stack overflow is to include what's called a "bootstrap condition" within the recursion. It's some condition that gets hit every time the function calls itself. You set the condition to something that causes the function to return when some state is reached, thereby unwinding the stack.
If a recursion never reaches a base case, it will go on making recursive calls forever and the program will never terminate. This is known as infinite recursion, and it is generally not considered a good idea. In most programming environments, a program with an infinite recursion will not really run forever.
There should always be two parts to a recursive function: the recursive case and the base case. The recursive case is when the function calls itself. The base case is when the function stops calling itself. This prevents infinite loops.
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.
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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With