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个回答

52

最佳的Node解决方案,文档链接:https://nodejs.org/api/timers.html#timerspromisessettimeoutdelay-value-options - kigiri
通过以下方式,您可以使其更加直观,轻松地将其重命名为“sleep”:import { setTimeout as sleep} from 'timers/promises';const { setTimeout: sleep } = require('timers/promises'); - Mercury
1
@Mercury,我采纳了你的建议。 - Mir-Ismaili

46

我个人偏爱简单:

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

然后:

await sleep(2000); // Sleeps for 2 seconds

我一直在使用它来创建虚假的加载时间,在p5.js中创建脚本时。


3
我认为这是主要问题的最优版本:它不会在循环内执行任何数学计算,只进行简单比较。尽管有点难读。 - hegez
13
你曾经检查过这个函数运行时的CPU使用率吗?如果给它足够的时间,它应该接近100%。NEVER EVER DO THAT. - koders
3
鉴于循环无论如何都将在固定的墙钟时间内运行,似乎优化循环有点失去了重点。 - ShadowRanger
@noego 怎么会这样?我刚在Node 10中进行了测试,根本没有CPU使用率的变化。 - melMass
5
这个函数会通过让CPU保持100%忙碌来阻塞Node线程n秒钟。由于这种方式会阻塞线程和极度消耗CPU资源,因此这种“解决方案”是极其糟糕的。等待必须是非阻塞的,因此应采用异步方式。 - Jeremy Thille
显示剩余2条评论

34

这里是代码,不要作为一个坏的开发者在网站上使用它,因为它是一个开发工具函数。

// Basic sleep function based on ms.
// DO NOT USE ON PUBLIC FACING WEBSITES.
function sleep(ms) {
    var unixtime_ms = new Date().getTime();
    while(new Date().getTime() < unixtime_ms + ms) {}
}

24
基本上与原帖的内容相同。 - Andrew Barber
12
更准确地说,这是OP要求的一个替代方案。 - WGroleau

31

这里有一个使用同步XMLHttpRequest的简单解决方案:

function sleep(n){
  var request = new XMLHttpRequest();
  request.open('GET', '/sleep.php?n=' + n, false);  // `false` makes the request synchronous
  request.send(null);
}

文件sleep.php的内容:

<?php sleep($_GET['n']);

现在使用以下方式进行调用:
sleep(5);

使用现有的服务器实现

如果您没有自己的应用服务器(用于上述 PHP 脚本),您可以使用一些在线服务来代替。例如:

function sleep(n) { 
    var request = new XMLHttpRequest();
    request.open('GET', 'http://httpstat.us/200?sleep=' + n, false);
    request.send(null);
};

sleep(1000);
console.log("one second delay completed.");

支持

关于将asynchronous参数设置为falsemdn指出:

在主线程上进行同步请求很容易干扰用户体验,应该避免;事实上,许多浏览器已经完全废弃了主线程同步XHR支持。同步请求在Worker中是允许的。

实际延迟

传递给服务器的毫秒数将会是服务器在接收请求和发送响应之间等待的时间。而由传输和服务器负载引起的延迟将被添加到该时间上。


5
如果可行的话,可以使用setTimeout(),但是如果这样做意味着要解开1000行的回调函数,那么这可能就不像一个玩笑了。 - pguardiario
13
独特的方法,但不幸的是非异步XMLHttpRequest已经过时,并将在未来被移除。这很有趣,因为这个事实正是我最初提出这个问题的原因。 - Beejor
很好,但你知道如果有时候网络变慢或者网站ping时间很长,那么脚本会休眠超过参数时间。比如说,如果你使用sleep(300)而网站响应需要150毫秒的时间,那么JavaScript代码将会休眠450毫秒。如果浏览器失去了网络连接,它只会工作0毫秒。因此,这不是一个更好的解决方案。 - Harendra Chauhan

30

一个内联样式:

(async () => await new Promise(resolve => setTimeout(resolve, 500)))();

这里的 500 是虚拟机在等待执行下一行代码之前等待的时间(单位为毫秒)。

简单来说:

创建一个 Promise 时,它返回一个可观察对象,同时在创建时提供一个回调函数的 resolve 引用,用于在数据/响应可用时传递。这里,经过 500 毫秒后通过 setTimeOut 调用 resolve ,在 resolve 没有执行之前,外部作用域会等待进一步处理,从而创建一个假的阻塞。这与非阻塞(或其他语言中可用的非线程保留睡眠)完全不同,因为线程以及可能的 UI 和网页/节点应用程序中的任何其他正在进行的任务将被阻塞,并且主线程将专门用于等待 Promise 解析。


我无法理解函数resolve被定义为调用自身的部分。这有什么意义吗? - sureshvv
@sureshvv,请检查更新。 - Ravinder Payal
如果想要在异步函数中间插入,请使用await new Promise(resolve => setTimeout(resolve, sleepMiliseconds)) - Matias Haeussler
1
这太棒了,因为它可以在同步的上下文中工作,非常感谢! - dreamflasher

28

使用Atomics.wait的2019更新

它应该在Node.js 9.3或更高版本中运行。

我需要一个相当精确的计时器在Node.js中,它对此非常有效。

然而,在浏览器中似乎支持极其有限。

let ms = 10000;
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);

运行了几个10秒计时器基准测试。

使用setTimeout,我得到了高达7000微秒(7ms)的误差。

使用原子操作,我的误差似乎保持在600微秒(0.6ms)以下。

2020年更新:总结

function sleep(millis){ // Need help of a server-side page
  let netMillis = Math.max(millis-5, 0); // Assuming 5 ms overhead
  let xhr = new XMLHttpRequest();
  xhr.open('GET', '/sleep.jsp?millis=' + netMillis + '&rand=' + Math.random(), false);
  try{
    xhr.send();
  }catch(e){
  }
}

function sleepAsync(millis){ // Use only in async function
  let netMillis = Math.max(millis-1, 0); // Assuming 1 ms overhead
  return new Promise((resolve) => {
    setTimeout(resolve, netMillis);
  });
}
function sleepSync(millis){ // Use only in worker thread, currently Chrome-only
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, millis);
}

function sleepTest(){
  console.time('sleep');
  sleep(1000);
  console.timeEnd('sleep');
}

async function sleepAsyncTest(){
  console.time('sleepAsync');
  await sleepAsync(1000);
  console.timeEnd('sleepAsync');
}

function sleepSyncTest(){
  let source = `${sleepSync.toString()}
    console.time('sleepSync');
    sleepSync(1000);
    console.timeEnd('sleepSync');`;
  let src = 'data:text/javascript,' + encodeURIComponent(source);
  console.log(src);
  var worker = new Worker(src);
}

其中服务器端页面,例如sleep.jsp,看起来像:
<%
try{
  Thread.sleep(Long.parseLong(request.getParameter("millis")));
}catch(InterruptedException e){}
%>

在我看来,这个解决方案比被接受的解决方案更好,因为被接受的解决方案在调用者中不能作为简单的函数实现,而需要使用async/await。 - GroovyDotCom
2
是的,只要您知道这是阻塞操作,并且通常不是一个好的选择。 - Kapytanhook
1
很酷,但事实上只有Chrome和Firefox真正支持这一点,这使得它在Web上的使用不太可行。(2019年11月) - JGreatorex
这就是我要找的答案!我没有访问异步函数的权限 :D - vitiral
在需要阻塞的罕见情况下,这是正确的解决方案。我希望在我写博客认为自己找到了一种新颖的解决方案之前,能够看到您的答案!无论如何,有关详细说明、演示以及使用 Service Worker 的XHR解决方案的另一种变体,请参考以下链接:https://jasonformat.com/javascript-sleep/ - Jason Miller

27

首先:

像这样定义一个你想要执行的函数:

function alertWorld(){
  alert("Hello, World!");
}

然后使用setTimeout方法安排其执行:

setTimeout(alertWorld, 1000)

请注意以下两点:

  • 第二个参数为毫秒数
  • 作为第一个参数,您需要传递函数的名称(引用),而不带圆括号

问题是要求以阻塞方式睡眠的方法。setTimeout不能做到这一点。它会排队在宏任务队列中。 - Aayush Sinha

21

让事情看起来更符合大多数人的期望的更好解决方案是使用匿名函数:

alert('start');
var a = 'foo';
// Lots of code
setTimeout(function(){  // Beginning of code that should run AFTER the timeout
    alert(a);
    // Lots more code
}, 5000);  // Put the timeout here

这可能是最接近你所需的东西了。

请注意,如果需要多次休眠,此方法可能会变得非常麻烦,你可能需要重新考虑你的设计。


这是在桌面浏览器和旧款手机上对我有效的一个。其他我尝试过的并不适用于所有情况。 - Mike

21

没有任何依赖关系的最短解决方案:

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

它在IE11中无法工作。箭头函数出现语法错误。 - DDphp
@Java-DK 使用 await new Promise(function(resolve) { setTimeout(resolve, 5000); }); - k06a
3
这与 Ahmed Mohammedali 的回答 完全相同(首先发布)。 - Peter Mortensen
2
@PeterMortensen 这是我在2018年3月给出的另一个答案的复制链接:https://dev59.com/1GYq5IYBdhLWcg3wui7F#49139664 - k06a

20

setTimeout是JavaScript异步方法的一部分(这些方法开始执行,其结果将在未来的某个时间排队到一个称为回调队列的组件中,稍后执行)

你可能想要做的是将setTimeout函数包装在一个Promise中。

promise示例:

const sleep = time => new Promise(res => setTimeout(res, time, "done sleeping"));

// using native promises
sleep(2000).then(msg => console.log(msg));

异步/等待示例:

const sleep = time => new Promise(res => setTimeout(res, time, "done sleeping"));

// using async/await in top level
(async function(){
  const msg = await sleep(2000);
  console.log(msg);
})();

了解更多关于setTimeout的信息


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