35

Consider the following Javascript code:

function(){
    setTimeout(function() {
        $("#output").append(" one ");
    }, 1000);
    setTimeout(function() {
        $("#output").append(" two ");
    }, 1000);
}

You can also see this example on jsfiddle.

Can I be sure that the value of #output is always "one two", in that order? Usually, I would handle this problem like this:

function(){
    setTimeout(function() {
        $("#output").append(" one ");
        $("#output").append(" two ");
    }, 1000));
}

But I can not do it that way because I get messages from a server which tells me which function to execute (in this example append "one" or append "two"), which I have to execute with a small delay.

I already tested this code in Internet Explorer 9, Firefox 14, Chrome 20 and Opera 12, and the output was always "one two", but can I be sure that this will always be the case?

Uooo
  • 6,204
  • 8
  • 36
  • 63
  • 1
    For what reason you need to delay execution? – bart s Aug 02 '12 at 05:39
  • Here you can find the specification for timers in HTML 5: http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#timers – some Aug 02 '12 at 05:50
  • One solution would be to delay your timers themselves. I've proposed a probable solution – Jibi Abraham Aug 02 '12 at 07:00
  • If you care about the execution order, you may want to take a look at this page http://www.onsip.com/blog/2012/06/29/avoiding-javascript-settimeout-and-setinterval-problems – Roland Pihlakas Jan 04 '15 at 18:29
  • possible duplicate of [Are equal timeouts executed in order in Javascript?](http://stackoverflow.com/questions/1776239/are-equal-timeouts-executed-in-order-in-javascript) – Roland Pihlakas Jan 14 '15 at 13:36

9 Answers9

17

The Spec is here.

My interpretation of setTimeout step 8 in section 7.3 is that the execution order is supposed to be guaranteed.

However, I investigated this issue because when the window is minimized and then maximised in Chrome, I was finding that timeouts set in events coming from external sources (like websockets or webworkers) were being executed in the wrong order. I assume this is a browser bug and will hopefully be fixed soon.

kybernetikos
  • 8,281
  • 1
  • 46
  • 54
  • 1
    Thanks for pointing to the spec! It helps me that you can say from your experience not to rely on this. – Uooo Jan 28 '13 at 06:58
  • I was not able to find section 7.3 at the link provided? Has the content changed? Also, I wound instead the following text: "This API does not guarantee that timers will run exactly on schedule. Delays due to CPU load, other tasks, etc, are to be expected." – Roland Pihlakas Jan 04 '15 at 18:25
  • 2
    Yes, quite a bit of time has passed now and the spec document has changed. The issue is not so much about timers firing exactly on schedule, it's more about whether they fire in the expected order, even if there is delay. – kybernetikos Feb 11 '15 at 22:05
  • I believe that because of the "turn" nature of Javascript, websockets or webworkers will execute in a different turn than the main application which, if the websocket/worker that triggers the #two addition returns too early, its turn may possibly execute faster than originally intended. – Keith Mattix Jun 12 '15 at 05:50
  • At the time, in Chrome multiple events that were coming in from the same source - a websocket - were being executed in the wrong order during minimize/maximise of the window. – kybernetikos Feb 14 '17 at 22:19
4

Play around with this in your fiddle

$(document).ready(function() {
    setTimeout(function() {
        $("#output").append(" one ");
    }, 1000);
});
$(document).ready(function() {
    setTimeout(function() {
        $("#output").append(" two ");
    }, 999);
});​

And you will see that both

output: one two
output: two one

are possible. So Speransky is right that you cannot rely on your timeouts executing in the same order always.

Note that I have change one time with 1ms to show that the timeout 1000ms can execute before the 999ms timeout.

EDIT: The below code can delay execution without any chance of two to be printed before one

function(){
    setTimeout(function() {
        $("#output").append(" one ");
       setTimeout(function() {
           $("#output").append(" two ");
       }, 100);
    }, 1000);
}
bart s
  • 5,068
  • 1
  • 34
  • 55
  • 6
    This is not the op asked, the execute order of `setTimeout` is fixed in his question, while yours are not. – xdazz Aug 02 '12 at 05:51
  • 1
    If inserting the first timer costs more than 1ms, then the result is `"one tow"`. But if it takes less than 1ms, the result is `"two one"`. – xiaowl Aug 02 '12 at 05:51
  • I played around with this in different browsers, but I always get the result `"two one"`, maybe that is because I have a quite fast PC ;) I can imagine that on other PCs the order maybe is different, but I am always using the same interval (if i can not ensure the order of execution as asked in my question, I have to look for another solution anyway). – Uooo Aug 02 '12 at 06:00
  • Check the link in the above comments from `some`, and see `This API does not guarantee that timers will run exactly on schedule. Delays due to CPU load, other tasks, etc, are to be expected.` and point 9 about the user-agent time. Even though the javascript executes on a single thread, the behaviour may be different from what you expect. I am using Chrome 20.0.1132.57 m for testing. Probably you are using a different browser. When we play around with this code it executes different. Highly dangerous if you rely on order of execution! – bart s Aug 02 '12 at 06:07
  • I think `not guarantee .. exactly on schedule` means that a `setTimeout(func, 1000)` timer is not guarantee to be executed after exactly 1000ms later. – xiaowl Aug 02 '12 at 06:11
  • The documentation from the link of `some` says that it can not be guaranteed that the `timers will run exactly on schedule`, and also the user-agent time says something like that. That means for me, if I want a timeout of 1000ms, it may be sometimes 900ms, dometimes maybe 1100ms, which would not matter for me if I can still ensure the order of execution (`"one two"`). The link `xiaowl` posted says that is ensured by the timers JavaScript uses. Do I get this right? I need just to rely on the order `"one two"`, no matter if `"two"` is printed e.g. 400ms after `"one"`. – Uooo Aug 02 '12 at 06:16
  • @w4rumy, it couldn't be 900ms if you want a timeout of 1000ms. – xiaowl Aug 02 '12 at 06:21
  • @w4rumy: Added another approach to delay the code and delay execution of two a bit more – bart s Aug 02 '12 at 06:25
3

Yes, because javascript code is executed in one single thread, all asynchronous events, like click, mousemove, are queued to execute. When you call setTimeout, the engine insert a timer into its queue to execute in future, at least after delay time. So the two setTimeout generate two timers, one after another.

You can have a look at How Javascript Timers Work by John Resig.

xiaowl
  • 5,177
  • 3
  • 27
  • 28
2

No, you cannot be sure. It is asynchronously.

But in fact it probably will be true, because of realization of the mechanism in browsers.

Danil Speransky
  • 29,891
  • 5
  • 68
  • 79
1

Yes, the output will always be "one two".

Because the first setTimeout always runs before the second setTimeout, if they have same delay time, then the callback function of first will always be executed before the callback function of the second.

xdazz
  • 158,678
  • 38
  • 247
  • 274
0

Update
I've updated your fiddle with this method. Check it out here

How about this method -- Get a list of things to do from your server --

//Example - append one, append two
var appendList = ['one', 'two']

//then do this
appendList.forEach(function(item, index){
    setTimeout(function(){ 
        $('#output').append(item)
    }, index * 50 + 1000);
});

This way, you decide the sequence.

Jibi Abraham
  • 4,636
  • 2
  • 31
  • 60
  • That does not answer my original question. I just wanted to explain why I can not use the second approach i showed in my question. If I can not ensure the order of execution using setTimeout() with the same interval, I have to look for another solution that suits in my application anyway. – Uooo Aug 02 '12 at 07:04
  • But I can not use the second solution. – Uooo Aug 02 '12 at 08:02
0

If events are synchronous, there is the Continuum function to run functions in sequence:

function keyedSequence(key, fn) {
  fn = fn || this;
  key.push({fn:fn});

  return function() {
    for(var i=0, n, full=1; i<key.length; i++) {
      n = key[i];
      if(n.fn == fn) {
        if(!n.called) n.called = 1, n.args = key.slice.call(arguments);
        if(!full) break
      }
      if(!n.called) full = 0
    }

    if(full) for(i=0; i<key.length; i++)
      n = key[i], key[i] = {fn:n.fn}, n.fn.apply(n, n.args);
  }
}
Function.prototype.seq = keyedSequence;

You provide an empty array as the key. Functions keyed with the same key will be grouped together.

window.onload = function() {
  var key = [];
  document.getElementById("test1").addEventListener('click', function1.seq(key), false);
  document.getElementById("test2").addEventListener('click', function2.seq(key), false);
}

Click test2, then click test1 and order of execution is still function1 then function2.

Another way of calling it is:

window.onload = function() {
  var key = [];
  document.getElementById("test1").addEventListener('click', keyedSequence(key, function1), false);
  document.getElementById("test2").addEventListener('click', keyedSequence(key, function2), false);
}
Kernel James
  • 3,752
  • 25
  • 32
0

It depends on the browser, because setTimout is part of the window object in the browser, not defined in ECMAScript.

Ini
  • 548
  • 7
  • 19
0

Per spec, the have the same task source, and things that have the same task source should be ordered. So yes.

Shuo Feng
  • 445
  • 7
  • 13