为什么这样的递归不会导致堆栈溢出? [英] Why such recursion not getting stack-overflowed?

查看:50
本文介绍了为什么这样的递归不会导致堆栈溢出?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm failing to figure out why calling recSetTimeOut() does not result in a stack overflow error, while recPromise() does.

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

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

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

Why does it happen? What is the difference between them?

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

Can you explain the process behind the scene?

编辑更多信息

Node.js v12.1.0 Chrome DevTools 上运行此代码段:

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

结果节点:没有错误.

结果 Chrome :没有错误.

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

结果节点:

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

FATAL ERROR: invalid table size Allocation failed - JavaScript heap out of memory

结果 Chrome :浏览器崩溃.

推荐答案

让我们依次看一下.

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

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

This is not actually recursion. You are registering recSetTimeOut with the scheduler. When the browser's UI thread goes idle, it will pull the next waiting function off the list, an invoke it. The call stack never grows; the scheduler (native code) will always be at the top of a very short call stack. You can verify this by emitting an exception and checking its call stack.

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

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

This is effectively an infinite loop that refuses to ever yield control back to the UI. Every time the promise resolves, a then handler is immediately invoked. When that completes, a then handler is immediately invoked. When that completes... The UI thread will starve, and UI events will never be processed. As in the first case, the call stack does not grow, as each callback is made by what is effectively a loop. This is called "Promise chaining". If a promise resolves to a promise, that new promise is then invoked, and this does not cause the stack to grow. What it does do, though, is prevent the UI thread from doing anything.

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

您可以使用 console.log((new Error()).stack)来确认两个堆栈跟踪实际上都是空的.

You can confirm both stack traces are practically empty by using console.log((new Error()).stack).

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

Neither solution should result in a Stack Overflow exception, though this may be implementation-dependent; the browser's scheduler might function differently from Node's.

这篇关于为什么这样的递归不会导致堆栈溢出?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆