为什么这样的递归不会导致堆栈溢出?

时间:2019-07-08 15:03:31

标签: javascript

我无法弄清楚为什么调用recSetTimeOut()不会导致堆栈溢出错误,而recPromise()却导致堆栈溢出错误。

const recSetTimeOut = () => {
  console.log('in recSetTimeOut');
  setTimeout(recSetTimeOut, 0)
};
recSetTimeOut();

const recPromise = () => {
  console.log('in recPromise');
  Promise.resolve().then(recPromise);
}
recPromise();

为什么会发生?它们之间有什么区别?

你能解释一下幕后的过程吗?


编辑更多信息

Node.js v12.1.0Chrome DevTools上运行此摘要:

const recSetTimeOut = () => { setTimeout(recSetTimeOut, 0); }
recSetTimeOut();

结果Node:没有错误。

结果Chrome:没有错误。

const recPromise = () => { Promise.resolve().then(recPromise); }
recPromise();

结果Node

  

致命错误:无效的表大小分配失败-JavaScript堆内存不足

结果Chrome:浏览器崩溃。

2 个答案:

答案 0 :(得分:5)

让我们依次看看。

const recSetTimeOut = () => {
  console.log('in recSetTimeOut');
  setTimeout(recSetTimeOut, 0)
};
recSetTimeOut();

这实际上不是递归。您正在向调度程序注册recSetTimeOut。当浏览器的UI线程空闲时,它将拉出列表中的下一个等待函数,并对其进行调用。调用堆栈永远不会增长。调度程序(本机代码)将始终位于非常短的调用堆栈的顶部。您可以通过发出异常并检查其调用堆栈来验证这一点。

  • 此函数实际上不是递归的;堆栈不会增长。
  • 每次调用后,它将控制权交还给UI线程,从而允许处理UI事件。
  • 仅当UI完成其工作并调用下一个计划的任务时,才会发生下一次调用。
const recPromise = () => {
  console.log('in recPromise');
  Promise.resolve().then(recPromise);
}
recPromise();

这实际上是一个无限循环,它拒绝将控制权交还给UI。每当承诺解决时,都会立即调用then处理程序。完成后,将立即调用then处理程序。完成后... UI线程将饿死,并且UI事件将永远不会被处理。与第一种情况一样,调用堆栈不会增长,因为每个回调都是通过有效的循环进行的。这称为“承诺链”。如果一个承诺解析为一个承诺,则将调用该新的承诺,这不会导致堆栈增长。但是,它 所做的是防止UI线程执行任何操作。

  • 实际上是一个无限循环。
  • 拒绝将控制权交还给用户界面。
  • 调用堆栈不会增加。
  • 立即调用下一个调用,并且存在极大的偏见。

您可以使用console.log((new Error()).stack)来确认两个堆栈跟踪都基本为空。

这两种解决方案都不会导致堆栈溢出异常,尽管这可能与实现有关;浏览器的调度程序的功能可能与Node的调度程序不同。

答案 1 :(得分:-6)

据我了解,您的问题之所以失败,是因为then在您调用它时不接受参数。

类似的事情可能会产生预期的结果...

const recPromise = async () => {
  return Promise.resolve(recPromise())
}