【问题标题】:Recursive setTimeout makes stack grow递归 setTimeout 使堆栈增长
【发布时间】:2020-07-20 08:57:24
【问题描述】:

据我从这两个答案中了解到:

setTimeout 被递归调用时,堆栈不应增长,但如果您在 FF 或 Chrome 中打开开发工具并运行此函数:

function recur(n = 10) {
  console.trace();
  console.log(n, '++++++++++++++++');
  if (n > 0) setTimeout(() => recur(--n), 1000);
}

recur()

您将看到以下内容:

console.trace() debugger eval code:2:11
    recur debugger eval code:2
    <anonymous> debugger eval code:8
10 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
9 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
8 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
7 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
6 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
5 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
4 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
3 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
2 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
1 ++++++++++++++++ debugger eval code:3:11
console.trace() debugger eval code:2:11
    recur debugger eval code:2
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    recur debugger eval code:4
    (Async: setTimeout handler)
    recur debugger eval code:4
    <anonymous> debugger eval code:8
0 ++++++++++++++++

这是为什么呢?

【问题讨论】:

  • @CertainPerformance,你知道我该如何确认吗?
  • 您可以通过观察内存分配来观察这一点。删除调试器的东西并运行recur(9007199254740991) 有和没有setTimeout。由于没有 setTimeout 的内存不足错误(甚至堆栈溢出),您会发现解释器崩溃(如果它是没有内存限制的解释器,您甚至可能会发现您的操作系统崩溃)。使用 setTimeout 只需要很长时间才能执行,但会使用恒定的内存量
  • 也许它正在保存堆栈帧描述以便可以跟踪它们,但不是实际帧,这样GC可以正常发生,但是用户仍然可以调试。

标签: javascript recursion stack settimeout


【解决方案1】:

Chrome 开发者工具中的异步堆栈跟踪...旨在让您保持理智,同时尝试找出您的函数为何处于 setTimeout() 递归循环中

javascript 线程模型是事件循环。 - https://flaviocopes.com/javascript-event-loop/

setTimeout() 在技术上不是递归的。它是一个异步函数调用。在线程内部,它同步返回一个数字 id(您可以在其上调用 clearTimeout()),然后作为副作用将您的函数添加到事件循环堆栈的末尾。它是一个单线程模型,因此一旦 javascript 完成了您当前的线程/事件,它将从堆栈顶部弹出下一个函数以执行。因此 1000 毫秒只是计划执行的最短时间,但如果它仍在执行之前的事件/线程,则可能需要更长的时间。

如果我在节点中运行此代码,我会得到:

node
Welcome to Node.js v12.5.0.
Type ".help" for more information.
> function recur(n = 10) {
...   console.trace();
...   console.log(n, '++++++++++++++++');
...   if (n > 0) setTimeout(() => recur(--n), 1000);
... }
undefined
> 
> recur()
Trace
    at recur (repl:2:11)
    at repl:1:1
    at Script.runInThisContext (vm.js:123:20)
    at REPLServer.defaultEval (repl.js:384:29)
    at bound (domain.js:415:14)
    at REPLServer.runBound [as eval] (domain.js:428:12)
    at REPLServer.onLine (repl.js:700:10)
    at REPLServer.emit (events.js:205:15)
    at REPLServer.EventEmitter.emit (domain.js:471:20)
    at REPLServer.Interface._onLine (readline.js:314:10)
10 ++++++++++++++++
undefined
> Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
9 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
8 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
7 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
6 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
5 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
4 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
3 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
2 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
1 ++++++++++++++++
Trace
    at recur (repl:2:11)
    at Timeout._onTimeout (repl:4:31)
    at listOnTimeout (internal/timers.js:531:17)
    at processTimers (internal/timers.js:475:7)
0 ++++++++++++++++

【讨论】:

  • 这很棒。谢谢。
猜你喜欢
  • 2019-06-23
  • 2021-12-08
  • 2011-03-23
  • 2011-04-04
  • 2012-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多