Javascript setTimeout有0个延迟问题

时间:2013-02-15 07:36:30

标签: javascript settimeout

我注意到一个奇怪的行为:如果我有一系列任务并希望推迟执行,那么我可以使用每个都有0延迟的setTimeout。 (见http://javascript.info/tutorial/events-and-timing-depth#the-settimeout-func-0-trick

一切都很完美:任务排队并尽快执行。

但是......如果调用各种setTimeout 非常接近,那么我发现有时(很少发生!)没有在正确的顺序中执行。 为什么呢?

3 个答案:

答案 0 :(得分:3)

没有人承诺过他们会以“正确”的顺序被解雇(具有相同超时的任务将按照设置为超时的顺序执行)。 setTimeout仅保证:

  • 每次超时只执行一次(除非页面在此期间死亡)
  • 每次超时都会在到期时执行。

没有关于执行顺序的消息。事实上,即使实现者尝试来保持秩序(即使是副作用),很可能没有足够的时间分辨率来为所有任务和二进制堆提供唯一的排序顺序(这可能在这里使用)不保留相等键的插入顺序。)

如果您想保留延期任务的顺序,则只应在前一个任务完成后将其排队。

这应该有效:

var defer = (function(){
  //wrapped in IIFE to provide a scope for deferreds and wrap
  var running = false;
  var deferreds = [];
  function wrap(func){
    return function(){
      func();
      var next = deferreds.shift();
      if(next){
        setTimeout(wrap(next),0);
      }else{
        running = false;
      }
    }
  }
  return function(func){
    if(running){
      deferreds.push(func);
    }else{
      setTimeout(wrap(func),0);
      running = true;
    }
  }
})()

演示:http://jsfiddle.net/x2QuB/1/

答案 1 :(得分:3)

您可以考虑使用jquery deferreds(或其他一些deferreds实现),它可以非常优雅地处理这种模式。

需要注意的重点是延迟完成的回调按照添加顺序执行。

 var createCountFn  = function(val){  
    return function(){ 
        alert(val)
    };
}

 // tasks 
var f1 = createCountFn(1),
    f2 = createCountFn('2nd'),
    f3 = createCountFn(3);

 var dfd = $.Deferred();
 dfd.done(f1).done(f2).done(f3);

 dfd.resolve(); 

demo

答案 2 :(得分:0)

HTML5草案规范指出setTimeout方法可以异步运行(暗示可能不会保留回调将被执行的顺序),这可能是您的浏览器正在做的事情。

  

The setTimeout() method must run the following steps:

     
    

<强> ...

         

6。返回句柄,然后异步继续运行此算法。

         

7。如果方法上下文是Window对象,请等待与方法上下文关联的Document完全处于活动状态,以进一步超时毫秒(不一定是连续的)。

  

在任何情况下,都可以通过执行与此类似的操作来解决此问题:

function inOrderTimeout(/* func1[, func2, func3, ...funcN], timeout */)
{   var timer; // for timeout later
    var args = arguments; // allow parent function arguments to be accessed by nested functions
    var numToRun = args.length - 1; // number of functions passed
    if (numToRun < 1) return; // damm, nothing to run
    var currentFunc = 0; // index counter
    var timeout = args[numToRun]; // timeout should be straight after the last function argument
    (function caller(func, timeout) // name so that recursion is possible
    {   if (currentFunc > numToRun - 1)
        {   // last one, let's finish off
            clearTimeout(timer);
            return;
        }
        timer = setTimeout(function () 
        {   func(); // calls the current function
            ++currentFunc; // sets the next function to be called
            caller(args[currentFunc], timeout);
        }, Math.floor(timeout));
    }(args[currentFunc], timeout)); // pass in the timeout and the first function to run
}