[英]Synchronizing loop in Node.js with setTimeout - strange, apparently intentional behavior
I was investigating a poorly-behaving piece of Node.js code we've got running in production, and I saw this odd little paradigm to intentionally create an infinite loop. 我正在研究生产中正在运行的性能不佳的Node.js代码段,并且看到了这个奇怪的小范例,有意创建了一个无限循环。 I hope it's an anti-pattern, but either way while I was playing around with it I found some strange behavior.
我希望这是一种反模式,但是在我玩它的任何一种方式中,我都发现了一些奇怪的行为。
The basic idea is that we have an outer
function that does some work that takes indeterminately long and then calls its callback, and an inner
function that also does some indeterminate work (that involves a database connection), but only after calling setTimeout(function() {outer(inner)}, someDelay)
. 基本思想是,我们有一个
outer
函数,该函数执行不确定的时间,然后调用其回调,以及一个inner
函数,也执行某些不确定的工作(涉及数据库连接),但仅在调用setTimeout(function() {outer(inner)}, someDelay)
。 So, ideally, both functions finish within 15 seconds, go to sleep, and then wake back up to re-sync the environment, but of course that's not always what happens. 因此,理想情况下,两个功能都将在15秒内完成,进入睡眠状态,然后唤醒以重新同步环境,但是当然这并不总是这样。
It seems obvious for a few reasons that this is a bad design, but regardless, I found the following toy example's behavior really surprising. 出于某些原因,这似乎是一个不好的设计,这似乎很明显,但是无论如何,我发现以下玩具示例的行为确实令人惊讶。
'use strict';
var outer = function(callback) {
setTimeout(doWork, 1000);
function doWork() {
console.log('Did outer work.');
callback();
}
}
var inner = function() {
setTimeout(function() {outer(inner)}, 2000);
process.stdin.resume();
process.stdin.once('data', function(data) {
console.log('Got ' + data);
process.stdin.pause();
console.log('Did inner work.');
});
}
outer(inner);
When we run this, the outer function "does its work" every second, resulting in an additional outer(inner)
call added to the stack every 2 seconds (I figure). 当我们运行它时,外部函数每秒执行一次“工作”,从而导致每2秒将额外的
outer(inner)
调用添加到堆栈中(我认为)。 Each of the calls to inner()
, though, blocks on waiting for stdin
(the idea was to simulate a database lock). 但是,对
inner()
每次调用都会在等待stdin
阻塞(该想法是模拟数据库锁)。 If I wait until outer()
is called 5 times before sending anything to stdin
, that same data is processed by all 5 instances of inner()
on the stack. 如果我等到5次调用
outer()
,然后再将任何内容发送到stdin
,堆栈中的inner()
的所有5个实例都会处理相同的数据。
I was really surprised by that - I guess I expected just one function call to accept that first input stdin
and the other four to block. 我对此感到非常惊讶-我想我只希望有一个函数调用可以接受第一个输入
stdin
,而其他四个要阻塞。 Again, this is a bad design, but - the question: Is this desirable? 同样,这是一个不好的设计,但是-问题:这是否合乎需要? Did someone intentionally make
setTimeout()
and the JS callstack to behave in this way, or is it a side-effect of some other language feature? 有人故意使
setTimeout()
和JS调用堆栈以这种方式运行,还是其他语言功能的副作用?
Or does this make sense, and I'm just thinking about it the wrong way? 还是有道理,而我只是在以错误的方式思考?
Thanks in advance, and I'm sorry that it's kind of a vague one. 在此先谢谢您,对不起,它有点含糊。
I hope it's an anti-pattern
我希望这是一种反模式
No, there's nothing wrong with it. 不,这没有错。 Most server-like applications are coded using an infinite loop - you want them to handle connections forever (that is, until you kill it or it crashes).
大多数类似服务器的应用程序都是使用无限循环进行编码的-您希望它们永远处理连接(即,直到您杀死它或崩溃)。
resulting in an additional outer(inner) call added to the stack every 2 seconds (I figure).
导致每2秒将额外的外部(内部)调用添加到堆栈中(我认为)。
No. setTimeout
is asynchronous, which means it returns immediately, the rest of the code runs to completion, and then the scheduled callback is executed on a fresh new call stack. 否
setTimeout
是异步的,这意味着它立即返回,其余代码运行完毕,然后在新的新调用堆栈上执行调度的回调。 The stack does not grow in this semi-recursive pattern. 堆栈不会以这种半递归模式增长。
Each of the calls to inner(), though, blocks on waiting for stdin (the idea was to simulate a database lock).
但是,对inner()的每次调用都会在等待stdin时阻塞(该想法是模拟数据库锁)。
No, once()
does not block. 不,
once()
不会阻塞。 It just installs a handler that will be executed once when the event occurs the next time. 它只是安装一个处理程序,该处理程序将在下次事件发生时执行一次。
I was really surprised by that if I wait until outer() is called 5 times before sending anything to stdin, that same data is processed by all 5 instances of inner() on the stack.
我真的很惊讶,如果我等到5次调用external()之后再将任何内容发送到stdin,则相同的数据将由堆栈上的inner()的所有5个实例处理。
Yes - there's 5 data
event handlers waiting for the event, and they all get executed with the input when the event is fired. 是的-有5个
data
事件处理程序正在等待事件,并且在触发事件时都将使用输入来执行它们。
If you wanted to block until the event occurs before continuing the recursion, you would need to put the setTimeout
that schedules the next read inside the data
event listener: 如果要在继续递归之前阻塞事件直到发生,则需要将
setTimeout
安排在data
事件侦听器中,以安排下一次读取:
function inner() {
process.stdin.resume();
process.stdin.once('data', function(data) {
console.log('Got ' + data);
process.stdin.pause();
console.log('Did inner work.');
setTimeout(function() {outer(inner)}, 2000);
});
}
You might even want to omit the timeout here and call outer(inner);
您甚至可能想忽略此处的超时,并调用
outer(inner);
directly. 直。
The behavior isn't surprising: You're hooking up multiple separate handlers to the stdin
data
event, so when the event runs, it calls each of those handlers. 这种行为不足为奇:您将多个单独的处理程序连接到
stdin
data
事件,因此在事件运行时,它将调用这些处理程序中的每个处理程序。 It's exactly like this: 就像这样:
process.stdin.once('data', function(data) {
console.log("callback1: " + data);
});
process.stdin.once('data', function(data) {
console.log("callback2: " + data);
});
process.stdin.once('data', function(data) {
console.log("callback3: " + data);
});
If you run that, then type something and press Enter, all three callbacks receive the event, because after all, all three callbacks are registered for the event. 如果运行该命令,然后键入一些内容并按Enter,则所有三个回调都将接收该事件,因为毕竟所有三个回调都已为该事件注册。
That's exactly what inner
is doing: Hooking the data
event for multiple callbacks. 这正是
inner
所做的:将data
事件挂接到多个回调中。
This version, showing the number of handlers hooked to the data
event (and when things are actually queued), may make things a bit clearer: 这个版本显示挂钩到
data
事件的处理程序的数量(以及实际排队的时间),可能会使事情更加清楚:
'use strict';
var handlerCounter = 0;
var outer = function(callback) {
console.log("outer called, queue doWork for 1s from now");
setTimeout(doWork, 1000);
function doWork() {
console.log('doWork called');
callback();
}
}
var inner = function() {
console.log('Queueing outer(inner) call for 2s from now');
setTimeout(outer, 2000, inner);
process.stdin.resume();
++handlerCounter;
console.log('Adding another handler to the `data` event, total will be: ' + handlerCounter);
process.stdin.once('data', function(data) {
--handlerCounter; // Since `once` removed it
console.log('Got ' + data + ', handlers now: ' + handlerCounter);
process.stdin.pause();
});
}
outer(inner);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.