Node.js:异步函数中的尾部调用是否有优化?

mor*_*der 3 recursion tail-recursion node.js

我正在使用节点 v8.10.0

上述问题解释了 Node.js 如何不再支持 TCO。我最近遇到了这样的函数问题:

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

该代码存在内存泄漏,通过将其更改为:

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

我很惊讶这实际上有效,因为在上面描述的问题中,它清楚地解释了 Node 8.x 不支持 TCO。那么,是否有什么特殊的事情可以实现 TCO?或者是因为它在引擎盖下使用了生成器,并且返回将生成器标记为已完成,因此可以丢弃堆栈?

mor*_*der 5

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

此代码片段会导致内存泄漏,因为注释中声明的变量无法被垃圾回收,因为解释器不知道它们不会再次被需要。例如,解释器此时并不知道在 后面没有一行await可能需要所有这些声明的变量。

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

在这个例子中,函数被返回,因此垃圾收集器可以安全地清理方法中声明的变量。请注意,堆栈会保留下来,如果此递归函数有太多迭代,则会抛出错误Maximum call stack size exceeded,但声明的变量存在于堆中,因此可以在保持堆栈信息完整的同时进行垃圾收集。