关于javascript:如何在正在阻止的nodejs中创建睡眠/延迟?

How to create a sleep/delay in nodejs that is Blocking?

我目前正在尝试学习nodejs,而我正在做的一个小项目正在编写一个API,以控制一些联网的LED灯。

控制LED的微处理器具有处理延迟,我需要将发送给微控制器的命令间隔至少100毫秒。 在C#中,我习惯于仅调用Thread.Sleep(time),但在node中没有找到类似的功能。

我发现了在节点中使用setTimeout(...)函数的几种解决方案,但是,这是异步的并且不会阻塞线程(这是我在此方案中需要的)。

有人知道阻塞睡眠或延迟功能吗? 最好是不只是旋转CPU并具有+ -10 ms的精度的东西?


Node本质上是异步的,这就是它的优点,所以您实际上不应该阻塞线程,但是由于这似乎是用于控制LED的项目,因此无论如何我都会发布一个工作参数,即使它不是好一个,不应该使用(严重)。

while循环将阻塞线程,因此您可以创建自己的睡眠功能

1
2
3
4
5
6
7
function sleep(time, callback) {
    var stop = new Date().getTime();
    while(new Date().getTime() < stop + time) {
        ;
    }
    callback();
}

用作

1
2
3
sleep(1000, function() {
   // executes after one second, and blocks the thread
});

我认为这是阻塞线程的唯一方法(原则上),使线程处于循环状态,因为Node没有内置任何阻塞功能,因为这一定会破坏异步行为的目的。


最好的解决方案是为您的LED创建单例控制器,该控制器将排队所有命令并以指定的延迟执行它们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function LedController(timeout) {
  this.timeout = timeout || 100;
  this.queue = [];
  this.ready = true;
}

LedController.prototype.send = function(cmd, callback) {
  sendCmdToLed(cmd);
  if (callback) callback();
  // or simply `sendCmdToLed(cmd, callback)` if sendCmdToLed is async
};

LedController.prototype.exec = function() {
  this.queue.push(arguments);
  this.process();
};

LedController.prototype.process = function() {
  if (this.queue.length === 0) return;
  if (!this.ready) return;
  var self = this;
  this.ready = false;
  this.send.apply(this, this.queue.shift());
  setTimeout(function () {
    self.ready = true;
    self.process();
  }, this.timeout);
};

var Led = new LedController();

现在您可以调用Led.exec,它将为您处理所有延迟:

1
2
3
Led.exec(cmd, function() {
  console.log('Command sent');
});


使用Node睡眠包。 https://www.npmjs.com/package/sleep。

在您的代码中,您可以使用

1
2
var sleep = require('sleep');
sleep.sleep(n)

睡眠特定的n秒。


只需使用child_process.execSync并调用系统的睡眠功能即可。

1
2
3
4
5
6
7
8
9
10
11
//import child_process module
const child_process = require("child_process");
// Sleep for 5 seconds
child_process.execSync("sleep 5");

// Sleep for 250 microseconds
child_process.execSync("usleep 250");

// Sleep for a variable number of microseconds
var numMicroSeconds = 250;
child_process.execFileSync("usleep", [numMicroSeconds]);

我在主应用程序脚本顶部的循环中使用此命令,以使Node等到连接网络驱动器后再运行其余应用程序。


借助ECMA脚本2017(受Node 7.6及更高版本支持),它成为了一种形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function sleep(millis) {
  return new Promise(resolve => setTimeout(resolve, millis));
}

// Usage in async function
async function test() {
  await sleep(1000)
  console.log("one second has elapsed")
}

// Usage in normal function
function test2() {
  sleep(1000).then(() => {
    console.log("one second has elapsed")
  });
}

我想出的最简单的真正同步解决方案(即没有收益/异步),可以在所有OS上运行而没有任何依赖关系,那就是调用节点进程来评估嵌入式setTimeout表达式:

1
2
const sleep = (ms) => require("child_process")
    .execSync(`"${process.argv[0]}" -e setTimeout(function(){},${ms})`);


我发现这里的东西几乎可以正常工作https://stackoverflow.com/questions/21819858/how-to-wrap-async-function-calls-into-a-sync-function-in-node-js-or-ja
文字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
`function AnticipatedSyncFunction(){
    var ret;
    setTimeout(function(){
        var startdate = new Date()
        ret ="hello" + startdate;
    },3000);
    while(ret === undefined) {
       require('deasync').runLoopOnce();
    }
    return ret;    
}


var output = AnticipatedSyncFunction();
var startdate = new Date()
console.log(startdate)
console.log("output="+output);`

唯一的问题是打印日期不正确,但该过程至少是顺序的。


使用本机插件实现非常简单,因此有人这样做:https://github.com/ErikDubbelboer/node-sleep.git


即使开发严格的硬件解决方案,也不必在Node.js中进行阻塞。请参阅不使用setTimeoutsetInterval setImmediate的temporal.js。而是使用setImmediatenextTick来提供更高分辨率的任务执行,并且您可以创建线性任务列表。但是您可以做到这一点而不会阻塞线程。


异步调用ping命令可阻止当前代码在指定的毫秒内执行。

  • ping命令是跨平台的
  • start /b表示:启动程序但
    不显示窗口。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
const { execSync } = require('child_process')
// delay(blocking) specified milliseconds
function sleep(ms) {
    // special Reserved IPv4 Address(RFC 5736): 192.0.0.0
    // refer: https://en.wikipedia.org/wiki/Reserved_IP_addresses
    execSync(`start /b ping 192.0.0.0 -n 1 -w ${ms} > nul`)
}

// usage
console.log("delay 2500ms start\t:" + (new Date().getTime() / 1000).toFixed(3))
sleep(2500)
console.log("delay 2500ms end\t:" + (new Date().getTime() / 1000).toFixed(3))

注意重要事项:以上并非精确的解决方案,它只是接近阻塞时间


您可以简单地使用ECMA6和gen-run库中引入的yield功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let run = require('gen-run');


function sleep(time) {
    return function (callback) {
        setTimeout(function(){
            console.log(time);
            callback();
        }, time);
    }
}


run(function*(){
    console.log("befor sleeping!");
    yield sleep(2000);
    console.log("after sleeping!");
});


阻塞主线程不是节点的一种好方法,因为在大多数情况下,有超过一个人在使用它。您应该结合使用settimeout / setinterval和回调。