Search code examples
javascriptsettimeoutexecution

Understanding of Javascript code execution order


I have a piece of Javascript code.

var i, _i, _len, _ref;

_ref = [1, 2, 3];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
  i = _ref[_i];
  setTimeout((function() {
    return console.log(i);
  }), 0);
}

It's generated by a piece of Coffeescript and that's why the weird names in it. When you execute the code, the output would be 3 3 3. Based on the execution result, it seems that the execution order is:

enter for loop
settimeout 1 with a function
settimeout 2 with a function
settimeout 3 with a function
settimeout 1 execute
settimeout 2 execute
settimeout 3 execute

It's a little different with my understanding. As I understand, the setTimeout in the for loop runs asynchronously. The setTimeout statement itself can be completed instantaneously. After some milliseconds (0 in our case), the function will start to execute in a separate context. So my expected execution order is:

enter for loop
settimeout 1 with a function
settimeout 2 with a function, settimeout 1 execute
settimeout 3 with a function, settimeout 2 execute
settimeout 3 execute

Based on the CPU load and the complexity of the function passed to setTimeout, the execution order may be different with what I described above. But the my point is, setTimeout could start executing right away and don't need to wait the for loop's completion.

Hope someone can help to clarify. I check MDN for setTimeout but can't get anything useful.


Solution

  • The best way for me to expain that was changing the understanding of what is any asynchronous call in JavaScript, and setTimeout in particular. Instead of thinking of setTimeout as of direct function call you may consider it as adding some event to the queue which will start running all events only after everything else is done.

    After you think that this is just adding the event, you understand how it naturally works in JavaScript. JavaScript applications are one-thread applications (most of the time). So that means that as long as your synchronous code is working it won't even start to run asynchronous requests, so won't even touch the queue.

    In other, more simple words, setTimeout(..., 0) is adding some kind of 'onSyncCodeDone' event.

    The only way how you can get your expected execution order is using recursion:

    var _ref = [1, 2, 3];
    
    function process(index) {
      if (index < _ref.length) {
        setTimeout((function() {
          process(index + 1);
    
          return console.log(index);
        }), 0);
      }
    }
    
    process(0);