2
votes

I'm using Node v8.10.0

The above question explains how Node.js doesn't support TCO anymore. I recently experienced an issue with a function like this though:

async function processBatch(nthBatch) {
    // do a bunch of async work and build up variables
    await processBatch(nthBatch + 1);
}

The code had a memory leak which was immediately fixed by changing this to:

async function processBatch(nthBatch) {
    // do a bunch of async work and build up variables
    return processBatch(nthBatch + 1);
}

I'm surprised that actually worked, because in the question described above, it clearly explains that TCOs aren't supported in Node 8.x. So is there something special going on that enables a TCO here? Or is it because it's using a generator under the hood and the return marks the generator as done, so the stack can be discarded?

1

1 Answers

4
votes
async function processBatch(nthBatch) {
    // do a bunch of async work and build up variables
    await processBatch(nthBatch + 1);
}

This snippet results in a memory leak because the variables declared at the comment cannot be garbage collected, because the interpreter doesn't know that they won't be needed again. e.g. the interpreter doesn't know at that point that there isn't a line after the await that might need all those declared variables.

async function processBatch(nthBatch) {
    // do a bunch of async work and build up variables
    return processBatch(nthBatch + 1);
}

In this example, the function is returned, so the garbage collector can safely clean up the variables declared in the method. Note that the stack sticks around, and if this recursive function has too many iterations there will be a Maximum call stack size exceeded error thrown, but the variables declared live in the heap and so can be garbage collected while keeping the stack information intact.