为了让一个函数的单个实例一直在运行,以下方法在某些时候会出现问题吗?
const Q = require('q');
(function main() {
Q.fcall(somePromiseFunction).then(_ => {
main();
});
})();
我想知道一段时间后递归内存堆栈是否会溢出而程序是否会终止?
答案 0 :(得分:2)
令人惊讶的是,不!我相信你的代码运行良好。
JavaScript中的异步操作(例如setTimeout()
,XMLHttpRequest
和Promise
使用称为Event Loop的内容在程序的正常流程之外执行。实际上,当异步事件发生时,它会被添加到队列中。 (完全独立于堆栈)。一旦堆栈清空,事件循环将开始处理这些排队的消息,逐个执行它们的相关功能。
几乎所有JavaScript中的异步都以这种方式工作,其中包括Q promises库。因此,就您的示例而言,这是(非常)简化的解释:
main()
,创建一个新的堆栈帧。Q.fcall(somePromiseFunction)
,创建第二个堆栈帧(可能是第三个,当调用somePromiseFunction
时。这会在后台启动异步操作,并将函数传递给{{1}设置为回调。then
现已返回,因此清除了这些堆栈帧。Q.fcall(somePromiseFunction)
也已到达终点,因此也会被清除 - 我们会重新回到空堆栈。main()
回调与之关联。这里要注意的重要一点是您的代码不会递归!您的then
回调仅从事件队列中调用,而不是由then
调用,因此堆栈能够在迭代之间无问题地清除。
我建议观看演讲'What the heck is the event loop anyway?',了解有关这一切是如何运作的更多信息 - 他们解释的原因是它最终为我点击了。
修改强> 在评论中回答您的问题时,我会澄清一些事情。递归函数,以其最简单的形式,如下所示:
main()
调用函数会分配一个堆栈帧,当它返回时会被清除。但是,如果没有某种突破循环的方式,像这样的递归函数永远不会返回 - 它将继续运行,无休止地分配堆栈帧,直到出现堆栈溢出错误。这并不意味着你不应该在JavaScript中使用递归 - 它有时候非常有用!但是你需要注意的是,如果它在没有返回的情况下进行过多的递归,那么你就会成为最重要的。
在回答你的另一个问题时 - 事件循环/你的回调并不等待function recursive() {
recursive();
}
特别结束,它只是等待堆栈清除。这里简化了堆栈在程序中的作用:
main()
将此与我无休止的重复功能相比较:
[]
[main]
[main, fcall]
[main, fcall, somePromiseFunction]
[main, fcall]
[main]
[main, then]
[main]
[] // Main returned, so the stack is clear
[yourCallback] // The event loop kicks in, runs your callback
[yourCallback, main]
... // Above steps repeat
[yourCallback] // Main returned
[] // Stack clears, so the event loop kicks in again