如何在Node.js(JavaScript)中等待?我需要暂停一段时间。

740

我正在为个人需求开发控制台脚本。我需要能够暂停一段较长的时间,但是从我的研究来看,Node.js没有相应的停止方式。在一段时间后,读取用户信息变得困难... 我看到了一些代码,但我认为它们必须包含其他代码才能正常工作,例如:

    setTimeout(function() {
    }, 3000);

然而,我需要这行代码后的所有内容在一段时间后执行。

例如,

    // start of code
    console.log('Welcome to my console,');

    some-wait-code-here-for-ten-seconds...

    console.log('Blah blah blah blah extra-blah');
    // end of code

我也看到过类似的事情

    yield sleep(2000);

但是Node.js无法识别这个。

我该如何实现这个延长的暂停时间呢?


4
@Christopher Allen,也许不是很相关,但可以胜任任务: require("child_process").execSync('php -r "sleep($argv[1]);" ' + seconds); 意思是通过Node.js的child_process模块执行一条命令,即在PHP中运行一个代码段来使程序等待指定的秒数。 - Haitham Sweilem
node-sleep npm 模块可能会有用(但是,我只会在调试时使用它)。 - julian soro
1
这回答解决了你的问题吗?JavaScript 中类似于 sleep() 函数的方法是什么? - Dan Dascalescu
2
请不要编写自己的 Promises!使用 import { setTimeout } from 'timers/promises' - Nick Grealy
27个回答

35
这个问题非常古老,但最近V8已经添加了可以实现OP所请求的生成器。 生成器通常是使用诸如suspendgen-run等库来协助异步交互最容易使用的工具。
以下是使用suspend的示例:
suspend(function* () {
    console.log('Welcome to My Console,');
    yield setTimeout(suspend.resume(), 10000); // 10 seconds pass..
    console.log('Blah blah blah blah extra-blah');
})();

相关阅读(推销自己的方式):发电机有什么大不了?


2
好的回答 - 但应该写成 yield setTimeout(suspend.resume(), 10000); - edhubbell
1
谢谢,@edhubbell。这个答案基于一个非常旧的挂起版本,但是你对最新版是正确的。我会更新答案。 - jmar777
这是哪个版本的Node? - danday74
1
@danday74 我记不清它们何时被取消标记,但它们自v0.12以来一直在--harmony标志后面存在,并且根据node.green的说法,它们至少从v4.8.4开始无需任何标志即可使用:http://node.green/#ES2015-functions-generators-basic-functionality。但请注意,较新的async/await语法现在提供了更好的解决方案,无需像suspend这样的额外库。有关示例,请参见此答案:https://dev59.com/1GYq5IYBdhLWcg3wui7F#41957152。自v7.10以来,异步函数已经可用(无需标志)。 - jmar777
这很复杂 截至2021年 - Dan Dascalescu

28
尝试使用 Promise,在 NodeJS 中对我有效。一个命令即可。
await new Promise(resolve => setTimeout(resolve, 5000));

或者将其作为一个函数在NodeJS中重复使用

const sleep = async (milliseconds) => {
    await new Promise(resolve => setTimeout(resolve, milliseconds));
}

使用该函数的方式如下

await sleep(5000)

23

在Linux/nodejs中,这对我有效:

const spawnSync = require('child_process').spawnSync;

var sleep = spawnSync('sleep', [1.5]);

它是阻塞的,但它不是繁忙等待循环。

你指定的时间以秒为单位,但可以是一个小数。我不知道其他操作系统是否有类似的命令。


sleep is pretty much omnipresent and a utiltiy arround since early UNIX days https://en.wikipedia.org/wiki/Sleep_%28Unix%29. Only "not rare" os it would maybe not exist is windows (however there you could attempt child_process.spawnSync('timeout', ['/T', '10']) - humanityANDpeace

19

Node 16有了一种新的简单方法来完成它。

import { setTimeout } from 'timers/promises'

console.log('before')
await setTimeout(3000)
console.log('after')

这与Maxim Orlov的答案完全相同。 - Mr. Polywhirl

14

我最近创建了一个更简单的抽象叫做wait.for,它可以在同步模式下调用异步函数(基于node-fibers)。也有一个基于即将推出的ES6生成器的版本。

https://github.com/luciotato/waitfor

使用wait.for,您可以调用任何标准的nodejs异步函数,就好像它是一个同步函数,而不会阻塞node的事件循环。

当您需要时,您可以按顺序编码,这对于简化个人脚本非常完美(我猜)。

使用wait.for,您的代码将是:

require('waitfor')

..in a fiber..
//start-of-code
console.log('Welcome to My Console,');
wait.miliseconds(10*1000); //defined in waitfor/paralell-tests.js - DOES NOT BLOCK
console.log('Blah blah blah blah extra-blah');
//endcode. 

任何异步函数都可以以同步模式调用。请查看示例。


TypeError: Object #<Object> has no method 'miliseconds' - CaffeineAddiction
//定义在waitfor/parallel-tests.js中,请从该文件中获取。 - Lucio M. Tato
2
在我从wait.for/parallel-tests.js获取它后,我遇到了另一个与未定义属性等相关的错误。因此,我也需要复制它们。为什么不以一种不需要这样做的方式组织代码呢? - Dmitry Koroliov
Wait.for和其他纤程解决方案为我打开了一个全新的世界!如果可以的话,我会投上一百万个赞。虽然大多数Node.js社区反对纤程,但我认为它们是非常棒的补充,当涉及到回调地狱时绝对有其用武之地。 - Levi Roberts

10

由于JavaScript引擎(v8)根据事件队列中的事件顺序运行代码,所以JavaScript并不严格按照指定的时间触发执行。也就是说,当您设置一些秒数以后执行代码时,触发代码纯粹基于事件队列中的顺序。因此,触发代码的执行可能会超过指定的时间。

因此,Node.js遵循以下原则,

process.nextTick()

使用setTimeout()可以延迟执行代码。例如:

process.nextTick(function(){
    console.log("This will be printed later");
});

2

随着ES6对Promise的支持,我们可以在不依赖任何第三方工具的情况下使用它们。

const sleep = (seconds) => {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, (seconds * 1000));
    });
};

// We are not using `reject` anywhere, but it is good to
// stick to standard signature.

然后像这样使用它:
const waitThenDo(howLong, doWhat) => {
    return sleep(howLong).then(doWhat);
};

请注意,doWhat函数成为new Promise(...)中的resolve回调函数。
另外,请注意这是异步睡眠。它不会阻塞事件循环。如果您需要阻塞睡眠,请使用此库,该库借助C++绑定实现阻塞睡眠。(尽管在Node等异步环境中需要阻塞睡眠的情况很少。) https://github.com/erikdubbelboer/node-sleep

1

使用 Promise 是 JavaScript 中实现“等待”的一种方法,正如顶部答案所示。

那么它应该如何使用呢?

这里有一个简单的例子,展示了如何以非阻塞的方式在 5 秒的子进程中排队参数,并将其传递给 4 秒的主进程。

const wait = (seconds) => 
    new Promise(resolve => 
        setTimeout(() => 
            resolve(true), seconds * 1000))

const process = async (items, prepTask, mainTask) => {
    const queue = [];
    let done = false;

    items.forEach((item, i) => {
        prepTask(item).then(() => {
            queue.push(item);
            if (i == items.length -1) {
                done = true;
            }
        })
    })

    while (!done || queue.length) {
        if (queue.length) {
            const workload = queue.shift();
            await mainTask(workload)
        } else {
            console.log('waiting for subtask to queue')
            await wait(1);
        }
    }
}

// Usage Example

const ids = [1,2,3,4,5,6,7,8,9,10];

const prepTask = async (id) => {
    await wait(id * 5)
    return id * 5;
}

const mainTask = async (workload) => {
    console.log('excuting workload: ', workload);
    const result = await wait(4);
    return { workload, result }
}

process(ids, prepTask, mainTask)
    .then(() => console.log('done'))

0

这是一个基于@atlex2建议的脏阻塞方法的moment.js风格模块。仅用于测试

const moment = require('moment');

let sleep = (secondsToSleep = 1) => {
    let sleepUntill = moment().add(secondsToSleep, 'seconds');
    while(moment().isBefore(sleepUntill)) { /* block the process */ }
}

module.exports = sleep;

0
let co = require('co');
const sleep = ms => new Promise(res => setTimeout(res, ms));

co(function*() {
    console.log('Welcome to My Console,');
    yield sleep(3000);
    console.log('Blah blah blah blah extra-blah');
});

上面的代码是解决JavaScript异步回调地狱问题的副作用。这也是我认为使JavaScript成为后端有用语言的原因。实际上,这是我认为现代JavaScript引入的最令人兴奋的改进。要完全理解它的工作原理,需要完全理解生成器的工作原理。在现代JavaScript中,跟随一个*function关键字称为生成器函数。npm包co提供了一个运行器函数来运行生成器。

本质上,生成器函数提供了一种使用yield关键字暂停函数执行的方法,同时,在生成器函数中使用yield使内部和调用者之间交换信息成为可能。这为调用者从异步调用的promise中提取数据并将已解析的数据传回生成器提供了机制。有效地使异步调用同步化。


虽然这段代码可能回答了问题,但提供有关它如何以及/或为什么解决问题的附加上下文将改善答案的长期价值。 - Donald Duck
感谢@DonaldDuck。我回答中的代码可能是JavaScript改进中最令人兴奋的部分。我很惊讶一些超级聪明的人会考虑这种解决回调地狱问题的方式。 - user1663023

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