Why the function called by setTimeout has no callstack limit?
setTimeout
is asynchronous (it returns before executing the callback), and the callback will be executed on a new, empty stack frame. That's the whole purpose. It will never overflow the stack.
It is not a recursive call, for which scope (in case of a non-tail-call-optimized function) would need to be retained. But that would also mean that the function became blocking, which is not what you want.
why does a function with setTimeout not lead to a stack overflow
Because it is no longer recursive. At least not technically.
The source code does look recursive so a programmer may write such code as if it is recursive but from the point of view of the CPU it is no longer recursive. It is processed sequentially in a loop.
Recursion and the stack
A recursive function calls itself. What happens when this happens is that the stack keeps increasing until the last function returns. A function's stack frame isn't removed from the stack until the function returns (let's ignore closures for now) so because recursive function calls itself it won't return until that call to itself returns. This is what causes the stack to grow.
Tail recursion
Languages such as Lisp, Haskell and Scala recognize that there are some cases where a stack frame can be released while doing recursion. Generally, if the recursive call is the last instruction in the function and no other processing is done to the return value you can remove the current stack frame because it will no longer be used after the recursive function returns. Therefore, such languages implement what's called tail recursion: the ability to recurse infinitely without growing the stack.
This is especially useful for very pure functional languages where the only programming structure you have is functions because without the existence of statements you cannot have loop statements or conditional statements etc. Tail recursion makes infinite loops in Lisp possible.
However, Javascript does not have tail recursion. So this does not affect how recursion behaves in Javascript. I mention this to note that not all recursion need to grow the stack.
Scheduling
Timer functions such as setTimeout()
and setInterval()
does not call the functions passed to them. Not only do they not call them immediately, they don't call them at all. All they do is pass the function to the event loop along with information of when should the function be called.
The event loop is essentially the core of javascript. The interpreter enters the event loop if and only if there is no more javascript to execute. You can think of the event loop as the idle state of the interpreter. The event loop continuously checks for events (I/O, UI, timer etc.) and execute the relevant functions attached to the event. This is the function you passed to setTimeout()
.
setTimeout
So with the facts given above we can see how "recursion" via setTimeout
is not really recursion.
First your function calls
setTimeout
and passes itself to it.setTimeout
saves the function reference to a list of event listeners and sets up the timer to trigger the event which will trigger the functionYour function continues and returns, note that the "recursed" function is not yet called. Since your function returns it's stack frame gets removed from the stack.
Javascript enters the event loop (there's no more javascript to process).
Timer for your function expires and the event loop calls it. Repeat until you stop calling
setTimeout
Does the set timeout web API have no memory limit?
To answer your question which is :
Does the set timeout web API have no memory limit?
I would say from what I found searching around to help you is that you may run into the problem of memory limitations available to node with setTimeOut function.
Read more about it here
Is there will be any stack overflow error with setTimeout() in 1st function and closure 2nd function in javascript?
No, there won't be.
Timers "set aside" the callback - they don't call them in the same execution stack as the one that's currently running (the one calling setTimeout
). JS will continue executing the current script until the stack is empty before calling any callback set by a timer (or any completed async operation for that matter).
In your case precalculationsInit
would "schedule" precalculationsInitClosure
, then finish executing, emptying the stack. Then at least 10ms later, JS would call the scheduled precalculationsInitClosure
, do stuff, then call precalculationsInit
and repeats the same procedure.
Your stack would look like:
(empty)
pI (schedules pIC)
(empty, JS looks for callbacks)
pIC
pIC -> pI (schedules pIC)
pIC
(empty, JS looks for callbacks)
pIC
pIC -> pI (schedules pIC)
pIC
(empty)
...and so on
As you can see, because of this "scheduling"/"setting aside", the stack does not build up like in recursion:
pI
pI -> pIC
pI -> pIC -> pI
pI -> pIC -> pI -> pIC
...and so on
Maximum call stack size exceeded error
It means that somewhere in your code, you are calling a function which in turn calls another function and so forth, until you hit the call stack limit.
This is almost always because of a recursive function with a base case that isn't being met.
Viewing the stack
Consider this code...
(function a() {
a();
})();
Here is the stack after a handful of calls...
As you can see, the call stack grows until it hits a limit: the browser hardcoded stack size or memory exhaustion.
In order to fix it, ensure that your recursive function has a base case which is able to be met...
(function a(x) {
// The following condition
// is the base case.
if ( ! x) {
return;
}
a(--x);
})(10);
By using javascript recursive setTimeout function, is it risky to get the stackoverflow?
By using javascript recursive setTimeout function, is it risky to get the stackoverflow?
No. setTimeout
registers a handler that will get called by the browser when the timer triggers. By the time that happens, the stack has unwound (if it hadn't, the task that scheduled the timeout wouldn't have ended, and the browser's UI would be locked up waiting for it to end).
By trying this example you can see in browser console that the stack grows.
No, the stack unwinds before the handler is next called. If you're referring to the async "stack" entries that Chrome's devtools show you, those aren't the real stack, and they're a devtools artifact. (Start your timer, watch for two ticks so you see the "async" entry on the second one, then close your console, wait two more ticks, and reopen it; notice that there aren't any "async" entries — at all, not even the one you saw it log before closing the console!)
Why such recursion not getting stack-overflowed?
Let's look at each in turn.
const recSetTimeOut = () => {
console.log('in recSetTimeOut');
setTimeout(recSetTimeOut, 0)
};
recSetTimeOut();
This is not actually recursion. You are registering recSetTimeOut
with the scheduler. When the browser's UI thread goes idle, it will pull the next waiting function off the list, an invoke it. The call stack never grows; the scheduler (native code) will always be at the top of a very short call stack. You can verify this by emitting an exception and checking its call stack.
- This function isn't actually recursive; the stack does not grow.
- It yields control back to the UI thread after each invocation, thereby allowing UI events to be processed.
- The next invocation only occurs once the UI is done doing its thing and invokes the next scheduled task.
const recPromise = () => {
console.log('in recPromise');
Promise.resolve().then(recPromise);
}
recPromise();
This is effectively an infinite loop that refuses to ever yield control back to the UI. Every time the promise resolves, a then
handler is immediately invoked. When that completes, a then
handler is immediately invoked. When that completes... The UI thread will starve, and UI events will never be processed. As in the first case, the call stack does not grow, as each callback is made by what is effectively a loop. This is called "Promise chaining". If a promise resolves to a promise, that new promise is then invoked, and this does not cause the stack to grow. What it does do, though, is prevent the UI thread from doing anything.
- Effectively an infinite loop.
- Refuses to yield control back to the UI.
- The call stack does not grow.
- The next call is invoked immediately and with extreme prejudice.
You can confirm both stack traces are practically empty by using console.log((new Error()).stack)
.
Neither solution should result in a Stack Overflow exception, though this may be implementation-dependent; the browser's scheduler might function differently from Node's.
Related Topics
Getting All Selected Checkboxes in an Array
Firestore: What's the Pattern for Adding New Data in Web V9
What Is a Practical Use for a Closure in JavaScript
How to Check If String Contains Substring
How to Search JSON Tree with Jquery
How Is JavaScript Single Threaded
How to Determine If an Image Has Loaded, Using JavaScript/Jquery
How to Get Subarray from Array
How to Give Keyboard Focus to a Div and Attach Keyboard Event Handlers to It
Typescript Type 'String' Is Not Assignable to Type
How to Get a JavaScript Object Property Name That Starts with a Number
Displaying a Number in Indian Format Using JavaScript
How to Pass Variable from Jade Template File to a Script File
Validate That a String Is a Positive Integer