如何在Node.js中创建一个阻塞的睡眠/延迟函数?

73

我目前正在尝试学习Node.js,我正在编写一个API来控制一些联网的LED灯。但是,控制LED的微处理器有一个处理延迟,我需要将发送给微处理器的命令间隔至少100毫秒。在C#中,我通常只需调用Thread.Sleep(time),但我在Node.js中没有找到类似的功能。

我已经找到了几个使用setTimeout(...)函数的解决方案,但这是异步的,并且不会阻塞线程(这是我在这种情况下所需要的)。是否有人知道一个阻塞的sleep或delay函数?最好不要只旋转CPU,并且精度为+-10毫秒?


4
听起来你在使用错误的工具。Node.js 的设计初衷是为了实现非阻塞式编程,如果你想要创建一种阻塞式的守护程序,应该考虑其他技术。在网络环境下实现大约10毫秒的精度,在大多数编程语言中都很容易实现。 - Elias Van Ootegem
1
或者,您可以按照“Node”的方式,使用异步风格重新设计程序逻辑。 - Passerby
1
你确定需要阻塞线程吗?如果你是nodejs的新手,可能只是因为你还不习惯异步思维/设计流程 :) 无论如何,这里有一个sleep包:https://npmjs.org/package/sleep(在支持的情况下使用真正的sleep,在Windows上使用busy-wait) - Supr
1
这并不一定意味着它必须是阻塞的,除非setTimeout的开销太大。我只是做了一个简单的测试:http://jsapp.us/#s445.js 运行这个程序最多使用setTimeout延迟6毫秒,而阻塞则会产生高达39毫秒的延迟(最坏的情况可能是由于服务器忙于其他事情,因此可能不适用于OP的情况)。但我同意,如果需要绝对100%的精度和可靠性,node.js可能不太合适。 - Supr
1
有些情况下确实需要阻塞解决方案,例如在调试时:模拟延迟同时保持对执行顺序的完全控制。Node.js没有提供直接的解决方案(尽管它可以),这是Node的缺陷,不应该被宣传为一个特性,也不应该被用作责备提出合理问题的提问者的借口。 - Marcin Wojnarski
显示剩余2条评论
13个回答

73

Node的本质是异步的,这也是它的优点,所以您真的不应该阻塞线程,但既然这似乎是用于控制LED项目,我仍然会发布一个解决方法,即使这不是一个很好的方法并且不应该使用(认真的)。

while循环会阻塞线程,因此您可以创建自己的sleep函数。

function sleep(time, callback) {
    var stop = new Date().getTime();
    while(new Date().getTime() < stop + time) {
        ;
    }
    callback();
}

用作

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

我认为这是阻塞线程的唯一方法(原则上),通过让它在循环中忙碌,因为Node没有内置任何阻止功能,否则这会有点违背异步行为的目的。


27
我认为,“不应该使用”是正确的说法,因为这是一种繁忙等待。 - Bakudan
8
@Bakudan - 这取决于情况,它不应该被用来作为一个 Web 服务器,但是 Node 被用于许多其他的事情,在这种情况下, OP 控制 LED 灯,并明确要求阻塞线程,这正是此代码所做的。 - adeneo
8
个人认为 Node.js 需要基于睡眠的阻塞。暗示 Node 的作者们知道所有情景是错误的。如果您需要减缓活动速度以便不会超负载其他人的服务器和资源怎么办?仅因为您有一百万封要发出的外发邮件而您选择的平台似乎不允许添加延迟,就把别人的邮件服务器耗尽是不好的网络礼仪。谢谢,adeneo。 - Michael Blankenship
2
有几个npm模块可用于进行基于睡眠的阻塞。 - Timothy Gu
2
大多数这些模块都是在发布此答案后编写的,并使用完全相同的技术。 - adeneo
显示剩余12条评论

43

在ECMA Script 2017中(由Node 7.6及以上版本支持),它成为了一行代码:

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

2
这是应用程序最实用的休眠方式。然而,人们应该明白这与睡眠系统调用(也称为Thread.sleep())不同,合同规定您让应用程序至少休眠给定的时间量,再加上回调在事件队列中排队的时间(通常少于10毫秒)。 - Elias Toivanen
12
这个函数是非阻塞的,但问题明确要求一个阻塞的解决方案。 - Marcin Wojnarski
这太棒了!HRJ,在我沮丧地无法让它工作之后,你帮了我一个大忙。(我是 TypeScript 的新手) - Sander Bouwhuis

42

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

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,它会为你处理所有的延迟:

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

12
我简直不能相信在NodeJS中,一些简单的事情会变得如此复杂。上帝保佑C#和/或Java! - TriCore
2
@TriCore 希望现在NodeJS支持ES6和async/await。因此,超时和异步操作不再那么复杂了。 - Leonid Beschastny
7
三元组:“上帝保佑Node使愚蠢的事情变得如此复杂,因为阻塞单个线程绝对是愚蠢的(这不适用于答案,因为它是非阻塞的技术上是错误的)。@LeonidBeschastny实际上,在提问时生成器和co已经存在。目前,async..await肯定是一种可行的方法。” - Estus Flask
ReferenceError: sendCmdToLed未定义。 - Jortega

37

只需使用child_process.execSync,并调用系统的休眠函数。

//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在运行应用程序其余部分之前等待网络驱动器连接。


child_process.execSync("sleep 1"); ^ 引用错误:未定义child_process - sapy
3
@sapy 你需要包含 child_process 模块。例如:const child_process = require("child_process"); - sffc

35

2
在Ubuntu上安装时遇到问题。这是解决方案。https://github.com/ErikDubbelboer/node-sleep/issues/19#issuecomment-137213184 - tanaydin
1
这不是一个优雅的方法 - 还需要安装Python。 - kiran01bm

8

我能提供的最简单的 真正同步 解决方案(即不使用 yield/async)是调用节点进程来评估一个内联的 setTimeout 表达式,在所有操作系统中都能够运行,而且没有任何依赖项:

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

3
这是一个不错的解决方案,使用 spawnSync() 可以避免转义问题:spawnSync(process.argv[0], ['-e', 'setTimeout(function(){},' + ms + ')']); 在 Windows 和 Linux 上都可以工作。 - bonger

5

正如 sleep 包的作者所建议的 *:

function msleep(n) {
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, n);
}

function sleep(n) {
  msleep(n * 1000);
}

最佳答案。一个很好的解决方案(虽然Nodejs似乎不允许同步等待,但比使用while进行繁忙等待更好,因为它会阻塞Node的事件队列)。 - Sohail Si

2

2
这主要用于调试目的或编写测试。在生产环境中使用阻塞式休眠是一个非常糟糕的想法。 - Leonid Beschastny
2
当然,在大多数情况下是这样的。但是“生产”是一个广泛的术语。如果您不需要为客户提供服务,那么您可以做阻塞式的事情。 - vkurchatkin
3
只要你知道自己在做什么,使用 JavaScript 不仅限于客户端和服务器应用程序。 - setec
如果“production”是一堆命令行工具供管理员使用(使用主代码库中的协议/客户端代码编写的节点),那么节点中缺乏同步/阻塞功能是一个非常麻烦的问题...在某些情况下,异步操作可能是“生产中非常糟糕的想法”,因为需要进行额外的测试... - Mark K Cowan
这应该是被接受的解决方案!它是唯一一个真正阻止事件循环的方法。 - Bamieh

2

我在这里找到了一个几乎可用的东西:https://dev59.com/52Eh5IYBdhLWcg3w9nhj

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

独特的问题在于打印的日期不正确,但是至少流程是顺序的。

1
你可以简单地使用 ECMA6 中引入的 yield 特性和 gen-run 库:
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!");
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接