繁体   English   中英

用setTimeout在Node.js中同步循环-奇怪的,显然是故意的行为

[英]Synchronizing loop in Node.js with setTimeout - strange, apparently intentional behavior

我正在研究生产中正在运行的性能不佳的Node.js代码段,并且看到了这个奇怪的小范例,有意创建了一个无限循环。 我希望这是一种反模式,但是在我玩它的任何一种方式中,我都发现了一些奇怪的行为。

基本思想是,我们有一个outer函数,该函数执行不确定的时间,然后调用其回调,以及一个inner函数,也执行某些不确定的工作(涉及数据库连接),但仅在调用setTimeout(function() {outer(inner)}, someDelay) 因此,理想情况下,两个功能都将在15秒内完成,进入睡眠状态,然后唤醒以重新同步环境,但是当然这并不总是这样。

出于某些原因,这似乎是一个不好的设计,这似乎很明显,但是无论如何,我发现以下玩具示例的行为确实令人惊讶。

'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);

当我们运行它时,外部函数每秒执行一次“工作”,从而导致每2秒将额外的outer(inner)调用添加到堆栈中(我认为)。 但是,对inner()每次调用都会在等待stdin阻塞(该想法是模拟数据库锁)。 如果我等到5次调用outer() ,然后再将任何内容发送到stdin ,堆栈中的inner()的所有5个实例都会处理相同的数据。

我对此感到非常惊讶-我想我只希望有一个函数调用可以接受第一个输入stdin ,而其他四个要阻塞。 同样,这是一个不好的设计,但是-问题:这是否合乎需要? 有人故意使setTimeout()和JS调用堆栈以这种方式运行,还是其他语言功能的副作用?

还是有道理,而我只是在以错误的方式思考?

在此先谢谢您,对不起,它有点含糊。

我希望这是一种反模式

不,这没有错。 大多数类似服务器的应用程序都是使用无限循环进行编码的-您希望它们永远处理连接(即,直到您杀死它或崩溃)。

导致每2秒将额外的外部(内部)调用添加到堆栈中(我认为)。

setTimeout是异步的,这意味着它立即返回,其余代码运行完毕,然后在新的新调用堆栈上执行调度的回调。 堆栈不会以这种半递归模式增长。

但是,对inner()的每次调用都会在等待stdin时阻塞(该想法是模拟数据库锁)。

不, once()不会阻塞。 它只是安装一个处理程序,该处理程序将在下次事件发生时执行一次。

我真的很惊讶,如果我等到5次调用external()之后再将任何内容发送到stdin,则相同的数据将由堆栈上的inner()的所有5个实例处理。

是的-有5个data事件处理程序正在等待事件,并且在触发事件时都将使用输入来执行它们。

如果要在继续递归之前阻塞事件直到发生,则需要将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);
    });
}

您甚至可能想忽略此处的超时,并调用outer(inner); 直。

这种行为不足为奇:您将多个单独的处理程序连接到stdin data事件,因此在事件运行时,它将调用这些处理程序中的每个处理程序。 就像这样:

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);
});

如果运行该命令,然后键入一些内容并按Enter,则所有三个回调都将接收该事件,因为毕竟所有三个回调都已为该事件注册。

这正是inner所做的:将data事件挂接到多个回调中。


这个版本显示挂钩到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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM