运行 函数一直在 node.js
running a function all the time in node.js
为了始终拥有一个函数的单个实例运行,下面的方式会不会在某些时候出现问题?
const Q = require('q');
(function main() {
Q.fcall(somePromiseFunction).then(_ => {
main();
});
})();
我想知道过一段时间递归内存栈会不会溢出导致程序终止?
令人惊讶的是,没有!我相信您的代码应该 运行 没问题。
JavaScript 中的异步操作,例如 setTimeout()
、XMLHttpRequest
和 Promise
利用称为 Event Loop 的东西在正常流程之外执行的程序。实际上,当异步事件将要发生时,它会被添加到 'queue'(与堆栈完全分开)。一旦堆栈清空,事件循环将开始处理这些排队的消息,一个一个地执行它们的关联函数。
JavaScript 中几乎所有异步的东西都是这样工作的,包括 Q promises 库。因此,就您的示例而言,这是对正在发生的事情的(非常)简化的解释:
main()
被调用,创建一个新的栈帧。
Q.fcall(somePromiseFunction)
被调用,创建第二个堆栈帧(并且可能是第三个,当 somePromiseFunction
被调用时。这会在后台启动异步操作,函数传递给 then
设置为回调。
Q.fcall(somePromiseFunction)
现在已经 returned,所以那些堆栈帧被清除了。
main()
也到达了终点,所以它也被清除了 - 我们回到了一个空堆栈。
- 异步操作完成后,一条消息将被推送到事件队列,您的
then
回调与之关联。
- 堆栈为空,因此事件循环开始处理。
- 你的回调被执行。
这里要注意的重要一点是你的代码不会递归!您的 then
回调只会从事件队列中调用,而不会被 main()
调用,因此堆栈可以在迭代之间毫无问题地清除。
我建议观看演讲 'What the heck is the event loop anyway?' 以了解有关这一切如何运作的更多信息 - 他们解释它的方式最终让我为之着迷。
编辑:
为了回答您在评论中提出的问题,我将澄清一些事情。最简单形式的递归函数如下所示:
function recursive() {
recursive();
}
调用一个函数会分配一个堆栈帧,它在 return 时被清除。然而,如果没有某种方法来跳出循环,像这个这样的递归函数永远不会 return - 它只会继续下去,无休止地分配堆栈帧,直到出现堆栈溢出错误。并不是说这意味着你不应该在 JavaScript 中使用递归——它有时非常有用!但是你需要注意,如果它在没有 returning 的情况下递归太多,你将到达堆栈的顶部。
在回答您的其他问题时 - 事件 loop/your 回调不会特别等待 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
将此与我不断重复的函数进行比较:
[]
[recursive]
[recursive, recursive] // Nothing stops the recursion,
[recursive, recursive, recursive] // so the stack is never going to clear!
为了始终拥有一个函数的单个实例运行,下面的方式会不会在某些时候出现问题?
const Q = require('q');
(function main() {
Q.fcall(somePromiseFunction).then(_ => {
main();
});
})();
我想知道过一段时间递归内存栈会不会溢出导致程序终止?
令人惊讶的是,没有!我相信您的代码应该 运行 没问题。
JavaScript 中的异步操作,例如 setTimeout()
、XMLHttpRequest
和 Promise
利用称为 Event Loop 的东西在正常流程之外执行的程序。实际上,当异步事件将要发生时,它会被添加到 'queue'(与堆栈完全分开)。一旦堆栈清空,事件循环将开始处理这些排队的消息,一个一个地执行它们的关联函数。
JavaScript 中几乎所有异步的东西都是这样工作的,包括 Q promises 库。因此,就您的示例而言,这是对正在发生的事情的(非常)简化的解释:
main()
被调用,创建一个新的栈帧。Q.fcall(somePromiseFunction)
被调用,创建第二个堆栈帧(并且可能是第三个,当somePromiseFunction
被调用时。这会在后台启动异步操作,函数传递给then
设置为回调。Q.fcall(somePromiseFunction)
现在已经 returned,所以那些堆栈帧被清除了。main()
也到达了终点,所以它也被清除了 - 我们回到了一个空堆栈。- 异步操作完成后,一条消息将被推送到事件队列,您的
then
回调与之关联。 - 堆栈为空,因此事件循环开始处理。
- 你的回调被执行。
这里要注意的重要一点是你的代码不会递归!您的 then
回调只会从事件队列中调用,而不会被 main()
调用,因此堆栈可以在迭代之间毫无问题地清除。
我建议观看演讲 'What the heck is the event loop anyway?' 以了解有关这一切如何运作的更多信息 - 他们解释它的方式最终让我为之着迷。
编辑: 为了回答您在评论中提出的问题,我将澄清一些事情。最简单形式的递归函数如下所示:
function recursive() {
recursive();
}
调用一个函数会分配一个堆栈帧,它在 return 时被清除。然而,如果没有某种方法来跳出循环,像这个这样的递归函数永远不会 return - 它只会继续下去,无休止地分配堆栈帧,直到出现堆栈溢出错误。并不是说这意味着你不应该在 JavaScript 中使用递归——它有时非常有用!但是你需要注意,如果它在没有 returning 的情况下递归太多,你将到达堆栈的顶部。
在回答您的其他问题时 - 事件 loop/your 回调不会特别等待 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
将此与我不断重复的函数进行比较:
[]
[recursive]
[recursive, recursive] // Nothing stops the recursion,
[recursive, recursive, recursive] // so the stack is never going to clear!