Reputation: 5133
Lets say I'm runnin some kind of long operation for a set of values.
The function that starts this operation is startNext()
and the very last line executed in it is to itself so its a recursive call like so:
function startNext(){
var val = getNextValue()
workOnValue(val)
.then(doSomeMoreWork)
.then(doMoreStuff)
.then(moree)
.then(startNext);
}
This will make the stack to grow as tail recursion does not work in JS (yet). Will changing the last line to:
.then(function(){setTimeout(startNext, 0)});
Work better? Will it not fill the stack beacuse it adds a new operation to the event loop ?
Upvotes: 4
Views: 434
Reputation: 67340
Yes, setTimeout
will prevent the stack from growing because the current function completes and the "recursive" call to itself gets placed on the event queue. It will also run slower because its not being directly called and instead processed via the queue.
To demonstrate and prove this try an experiment with Node.
Put the code sample below into a file and switch the simple
flag towards the bottom. You will see that the recurseSimple
function runs super fast and blows the stack very quickly. The recurseTimeout
runs slower but will run forever.
function recurseSimple(count) {
// Count: 15269, error: bootstrap_node.js:392
// RangeError: Maximum call stack size exceeded
try {
if (count % 10000 === 0) {
console.log('Running count:', count);
}
recurseSimple(count + 1);
} catch (e) {
console.log(`Simple count: ${count}, error:`, e);
}
}
function recurseTimeout(count) {
// No stack exceeded
try {
if (count % 10000 === 0) {
console.log('Running count:', count);
}
setTimeout(recurseTimeout.bind(null, count + 1), 0);
} catch (e) {
console.log(`Timeout count: ${count}, error:`, e);
}
}
const simple = false;
if (simple) {
recurseSimple(0);
} else {
recurseTimeout(0);
}
Exactly the same principal applies to promises. I didn't use promises here in order to keep this as simple as possible.
Upvotes: 3
Reputation: 14423
then
handlers are pushed out of the execution context stack, so it's already doing what you propose:
onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].
This applies to A+ promises.
Here's the 3.1 note for clarity:
Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.nextTick. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.
Upvotes: 1