代码执行中的同步延迟

60

我有一段代码需要在延迟一定时间后执行,比如5000毫秒。目前我使用的是setTimeout,但它是异步的,我希望代码能够等待其返回后再执行。我已经尝试过以下方法:

function pauseComp(ms) 
 {
     var curr = new Date().getTime();
     ms += curr;
     while (curr   < ms) {
         curr = new Date().getTime();
     }
 } 

但是我想延迟的代码使用raphaeljs绘制一些对象,而显示效果一点也不流畅。我试图使用doTimeout插件。我只需要一个延迟,因为延迟和要延迟的代码都在循环中。我不需要ID,所以没有使用它。

for(i; i<5; i++){ $.doTimeout(5000,function(){
         alert('hi');  return false;}, true);}

这段代码在等待5秒钟后显示第一个“Hi”,然后在第一次迭代后立即显示警报。 我想让它等待5秒钟再次显示警报,然后再等待并再次显示警报,以此类推。

欢迎提供任何提示/建议!

12个回答

80

对已有答案的变体,与该答案同样好。

另外,我同意优先选择setTimeout和异步函数调用的注意事项,但有时例如在构建测试时,您只需要一个同步等待命令...

function wait(ms) {
    var start = Date.now(),
        now = start;
    while (now - start < ms) {
      now = Date.now();
    }
}

如果你想要以秒为单位,就在 while 循环检查时将开始的毫秒数除以 1000...

=== 编辑 ===

我注意到我的答案已经排到了最前面,但实际上它不应该是最佳答案。那个答案是作为一种替代方案编写的,以防你的代码无法使用 async / await 或者你只需要等待短暂的时间(比如为了测试等待一两秒钟)。

最佳答案应该指出,异步/等待模式是更好的方法,可以显着减少能量和 CPU 周期的使用。

请参见下面 @michaelolof 的答案示例...

const wait = (msec) => new Promise((resolve, _) => {
  setTimeout(resolve, msec));
});

(async () => {
  console.log("Start...")
  await wait(5000);
  console.log("...End")
})();

13
请注意 CPU 使用情况。 - Joseph Lust
3
当然,@JosephLust。这也是为什么它只应该用于测试而不是生产代码的另一个原因。 - ThinkBonobo

37

如果你想利用新的async/await语法,你可以将set timeout转换为一个promise然后await它。

function wait(ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log("Done waiting");
      resolve(ms)
    }, ms )
  })
}  

(async function Main() {
  console.log("Starting...")
  await wait(5000);
  console.log("Ended!")
})();

36

同步等待(仅用于测试!):

const syncWait = ms => {
    const end = Date.now() + ms
    while (Date.now() < end) continue
}

使用方法:

console.log('one')
syncWait(5000)
console.log('two')

异步等待:

const asyncWait = ms => new Promise(resolve => setTimeout(resolve, ms))

使用方法:

(async () => {
    console.log('one')
    await asyncWait(5000)
    console.log('two')
})()

替代方案(异步):

const delayedCall = (array, ms) =>
    array.forEach((func, index) => setTimeout(func, index * ms))

使用方法:

delayedCall([
    () => console.log('one'),
    () => console.log('two'),
    () => console.log('three'),
], 5000)

11
使用新的 Atomics API,您可以启动同步延迟而不会出现性能峰值:
const sleep = milliseconds => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds)

sleep(5000) // Sleep for 5 seconds

console.log("Executed after 5 seconds!")

1
这具有非常有益的效果,使用了优化的线程休眠(而不是忙等待),因此CPU可以在同步睡眠期间空闲,而不是在循环中进行计算。但是,请记住,当前规范不允许在主线程上使用Atomics.wait,只能在工作线程上使用。 - John Weisz
1
一个可能的问题是:“如果在调用代理中不允许等待,则会抛出错误异常。(大多数浏览器不允许在浏览器的主线程上使用wait()。)”,参见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Atomics。由于我的浏览器还没有支持“SharedArrayBuffer”,所以我无法进行测试。 - Ciro Santilli OurBigBook.com

5

JavaScript是一种单线程语言。您不能将setTimeout与同步处理结合使用。会发生的事情是,计时器会过期,但JS引擎将等待当前脚本完成后再处理结果。

如果您想要同步方法,只需直接调用该方法!

如果您想要在setTimeout之后处理某些内容,请在timeout函数中包含它或调用它。


3

非超时循环(检查时间或将计数器设置为 1000000 或者其他值)会使浏览器挂起。使用 setTimeout(或 $.doTimeout 插件)是最佳方式。

在循环中创建超时不起作用,因为循环在继续之前不等待先前的超时发生,正如您所发现的那样。尝试更像这样的代码:

// Generic function to execute a callback a given number
// of times with a given delay between each execution
function timeoutLoop(fn, reps, delay) {
  if (reps > 0)
    setTimeout(function() {
                 fn();
                 timeoutLoop(fn, reps-1, delay);
               }, delay);
}

// pass your function as callback
timeoutLoop(function() { alert("Hi"); },
            5,
            5000);

我只是草草地拼凑了一下,虽然我相信它可以工作,但它还有几个方面可以改进,例如在“循环”内部,它可以将索引值传递到回调函数中,这样您自己的代码就知道它进行了多少次迭代。但希望它能让您入门。


1
除了setTimeout之外,还有更好的选择,OP请求同步方法。@ThinkBonobo回答正确。 - robsonrosa
1
@robsonrosa - OP还说:“我想延迟的代码是使用raphaeljs绘制一些对象,显示效果不太流畅。”你不能同时使用同步代码和绘图,因为屏幕在同步代码完成之前不会重新绘制。仅仅因为OP要求某事并不意味着它是可能的。 - nnnnnn
1
用户特别寻求同步解决方案 - setTimeout 不是同步的。 - Drenai
@Ryan - 根据我之前的评论,仅仅因为他们要求某事并不意味着它是可能的。同步延迟无法与他们正在进行的绘图共存,而他们抱怨这种绘图不够流畅。 - nnnnnn

2

JavaScript是单线程的

在JavaScript中,不可能进行同步延迟,因为它是一种单线程语言。浏览器(最常见的JS运行环境)有所谓的事件循环。所以,浏览器执行的所有操作都发生在这个循环中。当你在浏览器中执行脚本时,会发生以下情况:

  1. 事件循环调用你的脚本
  2. 逐行执行脚本
  3. 一旦脚本完成*,事件循环继续运行

请注意,所有这些都发生在事件循环的一个单独帧中!这意味着在脚本退出之前,没有其他操作(如渲染、检查用户输入等)可以发生。 (*) 异步JavaScript,例如setTimeout/Interval()requestAnimationFrame()不在主线程上运行。因此,从事件循环的角度来看,脚本已经运行完毕。

这意味着,如果JavaScript中存在同步延迟,整个浏览器都必须等待延迟完成,同时无法做任何事情。因此,JS中没有同步延迟,也不会有。

替代方案-也许?

替代方案取决于你想要做的实际事情。在我的情况下,我有一个requestAnimationFrame()循环。所以,我所需要做的就是存储时间,并在循环中检查旧时间和新时间之间的差异。

let timer =
{
   startTime: 0,
   time: 1000,     // time for the counter in milliseconds
   restart: true   // at the beginning, in order to set startTime
};

loop();
function loop()
{
   if(timer.restart === true)
   {
      timer.startTime = Date.now();
      timer.restart = false;
   }
   
   if((Date.now() - timer.startTime) >= timer.time)
   {
      timer.restart = true;
      console.log('Message is shown every second');
      // here put your logic 
   }

   requestAnimationFrame(loop);
}

2
我已经编写了一个简单的同步超时函数。它可以通过回调和非回调两种方式工作。
函数:
function wait(ms, cb) {
  var waitDateOne = new Date();
  while ((new Date()) - waitDateOne <= ms) {
    //Nothing
  }
  if (cb) {
    eval(cb);
  }
}

回调函数示例:
wait(5000,"doSomething();");

非回调示例:

console.log("Instant!");
wait(5000);
console.log("5 second delay");

4
使用 cb() 替代 eval(db)。将脚本保存在字符串中不是一个好的做法。用法:wait(5000, function(){console.log("test!")})。顺便说一句,如果需要回调函数,最好使用 setTimeout。 - user5147563
我认为使用 requestAnimationFrame(function(){}) 可能比在 while 循环中什么都不做更好,而在 while 循环中什么都不做会消耗大量 CPU 资源。 - gogog

0
受@andrew65952的启发,但更现代化和更快速。
function wait(ms) {
  const now = Date.now()
  while (Date.now() - now <= ms) { /* do nothing */}
}

0

以下是如何使用 JQuery doTimeout 插件的方法

jQuery('selector').doTimeout( [ id, ] delay, callback [, arg ... ] );

docs中得知:"如果回调函数返回true,doTimeout循环将在延迟后再次执行,创建一个轮询循环,直到回调函数返回非true值为止。"

var start = Date.now();
console.log("start: ", Date.now() - start);
var i = 0;
$.doTimeout('myLoop', 5000, function() {
  console.log(i+1, Date.now() - start);
  ++i;
  return i == 5 ? false : true;
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-dotimeout/1.0/jquery.ba-dotimeout.min.js"></script>


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