JavaScript中的sleep()是什么?

3710

有没有比以下的pausecomp函数(取自这里)更好的方式在JavaScript中实现sleep?

function pausecomp(millis)
{
    var date = new Date();
    var curDate = null;
    do { curDate = new Date(); }
    while(curDate-date < millis);
}

这不是JavaScript中的休眠-操作之间的延迟的重复;我需要在函数执行过程中进行真正的休眠,而不是在某段代码执行之前延迟。


273
这是一个可怕的解决方案 - 在不做任何事情的同时,你将会消耗大量的处理周期。 - 17 of 26
21
睡眠的唯一目的就是轮询或等待回调 - setInterval 和 setTimeout 都比睡眠更有效。 - annakata
65
看到人们在不理解原帖想法的情况下就拒绝了,这真是令人惊讶。有时你需要一次__真正的睡眠__。我现在需要一次真正的睡眠来测试在顶层窗口和 iframe 之间发布和接收消息时浏览器的行为。用 while 循环来保持忙碌似乎是唯一的方法。 - Devs love ZenUML
2
我想使用 sleep() 来计时动画。(我知道有更好的方法...)提问者提供的代码在 Chrome 中对我无效,因为浏览器不会在脚本中进行修改后立即更新 DOM,而是等待代码执行完成后再进行任何 DOM 更新,因此脚本会等待所有延迟的总和,然后一次性应用所有 DOM 更新。 - Ari Fordsham
16
@DevsloveZenUML,浏览器环境的设计师和开发人员为了用户的利益决定不满足你们的要求,因为在异步应用程序中赋予某人阻止整个页面的明确能力是疯狂的。 - Oleg V. Volkov
显示剩余5条评论
94个回答

5226

2017-2021更新

自2009年提出此问题以来,JavaScript已经发生了很大的变化。所有其他答案现在都已过时或过于复杂。以下是当前最佳实践:

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

或者作为一行代码:

await new Promise(r => setTimeout(r, 2000));

作为一个函数:
const sleep = ms => new Promise(r => setTimeout(r, ms));

或者在Typescript中:
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

使用方法如下:

await sleep(<duration>);

演示:

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function demo() {
    for (let i = 0; i < 5; i++) {
        console.log(`Waiting ${i} seconds...`);
        await sleep(i * 1000);
    }
    console.log('Done');
}

demo();

注意,
  1. await 只能在带有 async 关键字前缀的函数中执行,或者在越来越多的环境的脚本顶层执行。
  2. await 只会暂停当前的 async 函数。这意味着它不会阻塞脚本的其余部分的执行,在绝大多数情况下都是您想要的。如果您确实需要一个阻塞构造,请参见 这个答案 使用 Atomics.wait,但请注意,大多数浏览器将不允许在浏览器的主线程上执行它。

两个新的 JavaScript 特性(截至 2017 年)帮助编写了此“休眠”函数:

兼容性

如果由于某种原因你正在使用低于7版本的Node(该版本已在2017年停止更新),或者你要面向旧版浏览器,可以通过Babel(一种工具,将JavaScript和新功能转译为普通的JavaScript)来使用async/await,并且需要使用transform-async-to-generator插件进行转换。


11
这里的东西很棒。我想知道,当JS调用“睡眠”模式后,这对现代浏览器的“活动”/“非活动”状态有何影响或关联?浏览器能否像对一般JS的期望那样阻止睡眠,在变为“活动”时稍后重新调用,还是它有不同的行为? - Andre Canilho
30
目前有哪些浏览器支持这个功能?在这个解决方案被绝大部分浏览器,或至少是所有常见浏览器支持之前,我不会认为之前的解决方案已经“过时”。相反,我认为这个解决方案很有趣但是不可用/不实用,直到它有广泛的支持。 - Alvin Thompson
6
现代网站开发大多使用转译器,因此本地浏览器支持的重要性不如更干净、更具未来可扩展性的代码。无论如何,请查看 caniuse - Dan Dascalescu
11
@jacroe - 这个转译器可以处理箭头函数以及async/await语法(这些语法会导致IE浏览器出现错误)。 - Jaromanda X
9
等待2秒钟后继续执行的一行代码:await new Promise(r => setTimeout(() => r(), 2000)); - phil294
显示剩余26条评论

867

(请查看2016年更新版的答案)

我认为想要执行一个操作,等待一段时间后再执行另一个操作是完全合理的。如果你习惯于编写多线程语言,你可能会想到让线程休眠一段时间并等待唤醒。

问题在于JavaScript是单线程事件驱动模型。虽然在某些特定情况下,整个引擎等待几秒钟可能很好,但通常这是不好的做法。假设我想在编写自己的函数时利用你的函数?当我调用你的方法时,我的方法将全部冻结。如果JavaScript能够以某种方式保留函数的执行上下文,在某处存储它,然后将其带回并稍后继续执行,那么可以进行睡眠,但这基本上就成了多线程。

因此,你基本上只能使用其他人建议的方法--需要将代码拆分成多个函数。

因此,你提出的问题有些错误。无法以你想要的方式进行休眠,也不应该追求你建议的解决方案。


69
这并不是一个正确的答案。如果 JavaScript 没有 sleep 函数,那只是因为 ECMAScript 没有要求它。这是由负责设计 JavaScript 的机构所做出的设计选择。JavaScript 运行时本来可以在运行下一行代码之前等待一定的时间,但选择了不这样做。 - Didier A.
5
尽管不具有实时精度,JavaScript 可以完美地实现睡眠功能。毕竟它是一个基于事件的系统。如果异步调用完成,则会触发事件。我认为,在发出 sleep() 请求后,同样可以通过返回控制权给浏览器,直到休眠结束,并将控制权返回给调用函数来实现。是的,我也同意有时候使用睡眠功能很方便,特别是在你之前的开发人员把设计搞砸了,你别无选择,除了彻底重构这个问题,而你又没有时间去做。 - Lawrence
尝试 Hypnotic,其遵循此思路:http://coolwanglu.github.io/hypnotic/web/demo.html - Lu Wang
有一种情况,超时无论你如何重构都解决不了问题:如果你在服务器端运行,客户端正在等待数据,并且你没有直接访问连接以将其传递给超时回调。例如,在Meteor中,您可能正在运行一个方法。在这种情况下,您应该考虑使用future,如此处所述:https://dev59.com/wGcs5IYBdhLWcg3wynDD - Jameson Quinn
15
我同意为什么在JS中不可能使用sleep()的原因,并且通常有更好的方法来解决问题。但我认为引擎将所有事情联系在一起的方式是一个设计缺陷;语言本身可以有一个sleep()函数,它只对特定的脚本、页面或函数进行限制,而不会像疯子一样占用CPU并冻结应用程序。现在已经是2015年了,你不应该用while(1)来崩溃整个浏览器。我们有Flash之类的工具可以实现这样的功能。 - Beejor

797
在JavaScript中,我会重写每个函数,以便它能尽快结束。你需要让浏览器重新控制,以便它可以进行DOM更改。
每当我想在函数中间等待一段时间时,我都会重构代码,使用setTimeout()

编辑

任何语言中的“睡眠”或延迟功能都备受争议。有人会说应该始终有一个信号或回调来触发给定的功能,而其他人则认为有时候随意的延迟时间是有用的。我认为在这个行业里,没有哪条规则能够支配一切。
编写一个sleep函数很简单,在JavaScript Promises的帮助下更容易使用:
// sleep time expects milliseconds
function sleep (time) {
  return new Promise((resolve) => setTimeout(resolve, time));
}

// Usage!
sleep(500).then(() => {
    // Do something after the sleep!
});

201
闭包方式。function foobar(el) { setTimeout(function() { foobar_cont(el); }, 5000); } - chaos
77
好的,如果这段代码并不打算在网页中使用,怎么办? - Eugenio Miró
11
如果代码不打算用于网页,可以让主机的对象模型实现一个sleep方法。-- 我认为这个问题是针对DOM的,它暴露给在网页上运行的JavaScript。 - BrainSlugs83
37
@Nosredna 是的,我们知道如何进行异步调用,但这并不能帮助我们使用 sleep()。我希望我的调用按照特定顺序进行,并且按照特定顺序返回数据。我在一个五层循环中。我想阻塞执行。一个真正的 sleep 方法不会“减慢浏览器”的速度,它将控制权交还给浏览器和任何其他想要 CPU 时间的线程,同时仍然处于阻塞状态。 - BrainSlugs83
8
@Tim 循环安全版本:for(i=0; i<5; i++) { (function(i) { setTimeout(function() { console.log(i); }, 1000*i); })(i); } 该代码是一个循环,它使用闭包和setTimeout函数在每秒钟打印一个数字,并保证了循环变量i的正确性。 - sorki
显示剩余20条评论

345
Firebug(以及可能是其他JavaScript控制台)中,按下回车键后什么也不会发生,只有在指定的睡眠时间之后才会发生...
function sleepFor(sleepDuration){
    var now = new Date().getTime();
    while(new Date().getTime() < now + sleepDuration){ /* Do nothing */ }
}

使用示例:

function sleepFor(sleepDuration){
    var now = new Date().getTime();
    while(new Date().getTime() < now + sleepDuration){ 
        /* Do nothing */ 
    }
}

function sleepThenAct(){
    sleepFor(2000);
    console.log("Hello, JavaScript sleep!");
}

sleepThenAct()

注意:仅用于调试和开发

57
这不是一个答案。它与问题中的代码完全相同,只是稍微缩短了一些。 - jab
33
真的?在JS中忙等待好吗?还要等好几秒钟?如果我发现一个网站这样做,我会将其阻止。 - mafu
51
这就是为什么它标明“仅供调试/开发”... 翻白眼 - xDaizu
37
不要这样做。这会使CPU在执行的核心上达到100%并被阻塞。 - koders
6
这很有用,也许是命令行JavaScript应用程序中唯一的睡眠方式,因为async/await不起作用。 - Wheezil
显示剩余11条评论

202

我同意其他帖子的观点。繁忙的睡眠只是一个坏主意。

然而,setTimeout不会阻塞执行。它会在设置timeout后立即执行函数的下一行,而不是在超时后执行,所以这不能实现与sleep相同的任务。

做法是将函数分解为前部和后部。

function doStuff()
{
  // Do some things
  setTimeout(continueExecution, 10000) // Wait ten seconds before continuing
}

function continueExecution()
{
   // Finish doing things after the pause
}

确保你的函数名称仍准确描述每个部分正在执行的操作(例如,GatherInputThenWait和CheckInput,而不是funcPart1和funcPart2)

该方法实现了在超时之前不执行您决定的代码行,同时仍将控制返回给客户端 PC 以执行其排队的任何其他内容的目的。

正如评论中指出的那样,这在循环中绝对不起作用。您可以进行一些花哨(丑陋)的黑客攻击,使其在循环中起作用,但通常情况下,这只会导致灾难性的意大利面式代码。


12
没问题。这个情况变得棘手的地方在于当你有一个循环,甚至是嵌套循环时。你必须放弃使用for循环,并使用计数器代替。 - Nosredna
4
这不是一个答案。这更像是回答“如何异步执行函数”的问题,而不是“如何阻止代码执行”的问题。 - Deepak G M
6
不,你需要使用闭包。例如:function foo(index) { setTimeout(function() { foo_continue(index); }, 10000); }for(var X = 0; X < 3;X++) { foo(X); } - X 的值被传递给了 foo,然后在最终调用 foo_continue 时,在名为 index 的变量下得到了重用。 - Izkata
@Izkata:你尝试过使用foo(index) { console.log(c[index-1]);foo_continue(index) { c[index]=index; }吗?你会发现,无论是使用setTimeout还是虚构的sleep()命令,结果都不同。 - Alexander
2
当然会,因为setTimeout()的目的是通过异步运行代码来防止浏览器锁定。在setTimeout版本中将console.log()放入foo_continue()中,您将获得相同的结果。 - Izkata
显示剩余5条评论

173
请不要制作一个忙等待的睡眠函数,使用 setTimeoutsetInterval 就可以满足你的需求。
var showHide = document.getElementById('showHide');
setInterval(() => {
    showHide.style.visibility = "initial";
    setTimeout(() => {
        showHide.style.visibility = "hidden"
    }, 1000);
}, 2000);   
<div id="showHide">Hello! Goodbye!</div>

每两秒钟隐藏一秒钟的文本。这展示了如何使用 setInterval 和 setTimeout 每秒钟显示和隐藏文本。


4
并不是完全全部:setInterval 在轮询方面表现更好。 - annakata
32
除非需要睡眠同步,否则这是一个完全有效的问题。 - Aaron Dufour
60
我认为许多人可能会忘记JavaScript不仅是一种浏览器语言。这个人可能正在创建一个Node命令行实用程序,需要短暂暂停而无需处理setTimeout带来的所有变量作用域问题。 - Phil LaNasa
6
如果语法闭包仍然让某人感到害怕,那么他们需要认真学习Node 101,并加以努力。 - chaos
5
@PhilLaNasa:任何不是JS 101的闭包上下文都需要进行全面的课程重新设计。 - chaos
显示剩余8条评论

114

如果你和我一样正在使用Rhino来编写JavaScript,那么你可以使用...

try
{
  java.lang.Thread.sleep(timeInMilliseconds);
}
catch (e)
{
  /*
   * This will happen if the sleep is woken up - you might want to check
   * if enough time has passed and sleep again if not - depending on how
   * important the sleep time is to you.
   */
}

30
不正确。这是使用Rhino的JavaScript(现在可能是Nashorn) - mjaggard

76

使用:

await new Promise(resolve => setTimeout(resolve, 2000));

在一个异步函数内部。

这只会产生“ReferenceError:wait未定义”的错误。 - Philipp Ludwig

74

如果你在使用jQuery,有人实际上创建了一个“delay”插件,它只是setTimeout的一个包装器:

// Delay Plugin for jQuery
// - http://www.evanbot.com
// - © 2008 Evan Byrne

jQuery.fn.delay = function(time,func){
    this.each(function(){
        setTimeout(func,time);
    });

    return this;
};

然后你可以像预期的那样,在一系列的函数调用中使用它:

$('#warning')
.addClass('highlight')
.delay(1000)
.removeClass('highlight');

4
这不是一个坏的解决方案。保持上下文和可链接性。 - Nosredna
42
从 jQuery 1.4 开始,.delay() 已成为 jQuery 的一部分(尽管其语义与上述实现有所不同)。http://api.jquery.com/delay/ - Matt Ball
11
这个问题明显缺乏 jQuery 的回答。很高兴我们得到了它! - xDaizu
1
如果你需要在两个独立的调用之间有延迟,那么是的。但如果你需要减慢循环速度,则不需要延迟。 - WGroleau

52

我也搜索了解决睡眠问题的方法(仅用于开发和测试,而非生产代码),找到了这篇文章:

JavaScript sleep()或wait()

...... 这里还有一篇关于客户端方案的文章:JavaScript sleep

另外,在调用 alert() 时,你的代码也会被暂停,直到警告显示出来——你需要找到一种不显示警告,但获得同样效果的方法。 :)


46
我同意,很多人都在说:“不要在生产代码中这样做!”是的,嗯,我也不想这样做。我想在一次性测试代码中这样做,并因此不想花费太多时间来制作一个优雅的解决方案。 - user435779

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