使用 setTimeout 会阻止堆栈增长吗?

Will using setTimeout prevent the stack from growing?

假设我正在对一组值进行某种长时间的操作。

启动这个操作的函数是startNext()

最后一行执行的是它自己,所以它是一个递归调用,如下所示:

function startNext(){
   var val = getNextValue()
   workOnValue(val)
      .then(doSomeMoreWork)
      .then(doMoreStuff)
      .then(moree)
      .then(startNext);
}

这将使堆栈增长,因为尾递归在 JS 中不起作用(目前)。 将最后一行更改为:

.then(function(){setTimeout(startNext, 0)});

工作更好? 它不会填充堆栈,因为它向事件循环添加了一个新操作吗?

then 处理程序被推出执行上下文堆栈,因此它已经在执行您的建议:

onFulfilled or onRejected must not be called until the execution context stack contains only platform code. [3.1].

这适用于 A+ 承诺。

为清楚起见,这里是 3.1 注释:

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.

Promises A+

是的,setTimeout 将阻止堆栈增长,因为当前函数已完成并且对自身的 "recursive" 调用被放置在事件队列中。它还会 运行 变慢,因为它不是直接调用而是通过队列处理。

为了证明这一点,请尝试使用 Node 进行实验。

将下面的代码示例放入文件中,并将 simple 标志切换到底部。您会看到 recurseSimple 函数 运行 非常快,并且很快就会炸毁堆栈。 recurseTimeout 运行 速度较慢,但​​会 运行 永远。

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);
}

完全相同的原则适用于承诺。为了尽可能简单,我在这里没有使用 promises。