【问题标题】:Synchronous delay in code execution代码执行的同步延迟
【发布时间】:2011-10-18 19:27:52
【问题描述】:

我有一个代码需要在延迟一段时间后执行,比如 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 秒再次发出警报等待然后发出警报等等。

感谢任何提示/建议!

【问题讨论】:

    标签: javascript jquery settimeout


    【解决方案1】:

    接受的答案的变化与这个一样好。

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

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

    如果您希望以秒为单位,请在 while 检查中将 start ms 除以 1000...

    【讨论】:

    • 请注意 CPU 使用情况。
    • 绝对是,@JosephLust。它应该只用于测试而不是生产代码的另一个原因。
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

      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)
      

      【讨论】:

        【解决方案4】:

        使用新的 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!")
        

        【讨论】:

        • 这具有使用优化的线程睡眠(而不是忙于等待)的非常有益的效果,因此 CPU 可以在同步睡眠期间空闲而不是处理循环。但是请记住,当前规范不允许在主线程上使用Atomics.wait,只能在工作线程上使用。
        • 一个可能的问题是“如果在调用代理中不允许等待,那么它会抛出一个错误异常。(大多数浏览器不允许在浏览器的主线程上等待()。)”:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…无法测试自己,因为我的浏览器还没有 SharedArrayBuffer
        【解决方案5】:

        JavaScript 是一种单线程语言。您不能将setTimeout 和同步处理结合起来。会发生什么,定时器会超时,但是 JS 引擎会等待处理结果,直到当前脚本完成。

        如果要同步方法,直接调用方法即可!

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

        【讨论】:

          【解决方案6】:

          非超时循环(检查时间或计数到 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);
          

          (我只是快速拼凑起来,所以虽然我相信它可以工作,但可以通过多种方式进行改进,例如,在“循环”中,它可以将索引值传递给回调函数,以便您自己的代码知道它要进行哪次迭代。但希望它能让你开始。)

          【讨论】:

          • 除了 setTimeout 是一个更好的选择,OP 请求同步方法。 @ThinkBonobo 回答正确
          • @robsonrosa - OP 还说“我想延迟的代码是使用 raphaeljs 绘制一些对象,并且显示一点也不流畅”。您不能使用同步代码绘制东西,因为在同步代码完成之前不会重新绘制屏幕。仅仅因为 OP 要求某些东西并不能使它成为可能。
          • 用户特意找同步解决方案——setTimeout不同步
          • @Ryan - 根据我之前的评论,仅仅因为他们要求某些东西并不能使它成为可能。同步延迟不能与他们正在做的绘图共存,他们抱怨不够流畅。
          【解决方案7】:

          我做了一个简单的同步超时功能。它以两种不同的方式工作,回调和非回调。

          功能:

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

          【讨论】:

          • 使用cb() 而不是eval(db)。将脚本保存在字符串中不是一个好习惯。用法:wait(5000, function(){console.log("test!")})。顺便说一句,如果需要回调,最好使用 setTimeout
          • 我认为在while 循环中使用requestAnimationFrame(function(){}) 可能比nothing 更好,而在while 循环中什么也不做会消耗很多cpu。
          【解决方案8】:

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

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

          来自docs:“如果回调返回true,则doTimeout循环将在延迟后再次执行,创建一个轮询循环,直到回调返回一个非真正的价值。"

          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>

          【讨论】:

            【解决方案9】:

            使用函数发生器的解决方案。以表明它可以做到。 不推荐

            function wait(miliseconds){
            
              const gen = function * (){
                 const end = Date.now() + miliseconds;
                 while(Date.now() < end){yield};
                 return;
              }
              
              const iter = gen();
              while(iter.next().done === false);
            }
            
            
            console.log("done 0");
            wait(1000);
            console.log("done 1");
            wait(2000);
            console.log("done 2");

            【讨论】:

            • 这个生成器函数没有意义,它可以被简单地内联。这与其他答案中的解决方案相同,但不必要地复杂。
            • @Bergi 感谢您的意见。不过,我不同意不必要的复杂。当使用函数生成器完成 async/await 时,它是相同的论点;一个复杂的包装器,只是为了使用带有底层 async/await 的函数生成器,但它被广泛使用。
            • 这与 async/await 或以特定方式运行用户提供的生成器无关 - 它甚至不是异步的。它是wait 内部的本地函数,值得简化。在这里使用生成器绝对没有优势 - 唯一的区别是它会在不同的指令上消耗 CPU 周期。
            【解决方案10】:

            节点解决方案

            使用 fs.existsSync() 来延迟

            const fs = require('fs');
            const uuidv4 = require('uuid/v4');
            
            /**
             * Tie up execution for at-least the given number of millis.  This is not efficient.
             * @param millis Min number of millis to wait
             */
            function sleepSync(millis) {
                if (millis <= 0) return;
                const proceedAt = Date.now() + millis;
                while (Date.now() < proceedAt) fs.existsSync(uuidv4());
            }
            

            fs.existsSync(uuidv4()) 旨在做一些事情:

            1. 通过生成uuid并查找不存在的文件来占用线程
            2. 新的 uuid 每次都会破坏文件系统缓存
            3. 查找文件可能是一种优化操作,应该允许其他活动继续进行(即不固定 CPU)

            【讨论】:

            • 如果您投反对票,请解释原因。我发现这种技术是有效的,但如果它是错误的,我真的很想知道为什么。我希望 fs.existsSync() 不会固定 CPU,这是有益的。
            • 您没有解释uuid/v4 的用途。作为 Javascript 的初学者,我想知道每段代码的作用以及它为什么适合您。
            • 谢谢@jramos775,我现在看到不是很明显。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-12-11
            • 1970-01-01
            • 2013-10-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多