By Need4Steed


2016-06-03 11:26:47 8 Comments

I need to call some functions sequentially, and in order to force the framework to do a dirty check after each step, I employed setTimeout(func,0) to trigger the dirty check mechanism.

I know simply calling setTimeout one by one won't guarantee the passed async functions would be invoked in expected order, therefore I kludged the following solution:

function foo(arg){
  setTimeout(()=>console.log('executing task' + arg),0);
  console.log('on call stack' + arg);
  return foo;
}

I tried foo(1)(2)(3)(4)(5), it works fine. But I'm not sure it will always work correctly.

So could anybody please help me with it!


@Steffomio's answer can definitely make the queued task deterministic, it also makes sure that each task has its own event loop ensuing.

Here is my adapted version:

function queueTask(task) {
  var queue = [];
  function nextTask() {
    setTimeout(function () {
      queue.length && queue.shift()(); taskCount++;
      queue.length && nextTask();
    }, 0);
  }
  return (function pushTask(task) {
    queue.push(task);
    //After the first call trigger the timeout asynchrony
    if (queue.length === 1) { nextTask(); } 
    return pushTask;
  })(task);
}

//Test part below
function t(arg) { return function () { console.log('Task ' + arg); } }

var taskCount = 0;
var beginTime = Date.now();

queueTask(t(1))(t(2))(t(3))(t(4))(t(5))
  (t('a'))(t('b'))(t('c'))(t('d'))(t('e'))
  (t(1))(t(2))(t(3))(t(4))(t(5))
  (t('a'))(t('b'))(t('c'))(t('d'))(t('e'))  
  (function () { console.log(taskCount + ' tasks executed, Time elapsed: ' + (Date.now() - beginTime)); });

After some research I learned that the callback passed to setTimeout will be called by the system only when the call stack is cleared (no more code on it), thus the actual execution of queued tasks won't start until the the queuing is done, and if we queue several 0-delay timeout tasks linearly, when the next event loop starts, they will all be executed in a single run. That is not what I want! So, calling setTimeout inside the callback of the preceding setTimeout is the only way so far I know to enforce tick by tick tasks scheduling.

For better understanding, please reference the talk "What the heck is the event loop anyway" given by Philip Roberts at JSConf EU 2014

2 comments

@Steffomio 2016-06-03 12:12:46

You need a queue:

q = [];
addQueue(1)(2)(3)(4)(5);
addQueue('a')('b')('d');
addQueue(1)(2)(3)(4)(5);
addQueue('a')('b')('d');
addQueue(1)(2)(3)(4)(5);
addQueue('a')('b')('d');

function queue(){
    if(q.length){
        setTimeout(function(){
            console.log('queue: ' + q.shift()); 
            q.length && queue();
        }, 1000);
    }
}

function addQueue(n){
    if(q.length){
        q.push(n);
    }else{
        q.push(n);
        queue();
    }
    return addQueue;
}

for testing copy and paste code to console.

@Need4Steed 2016-06-03 15:29:36

I'm not sure this will definitely work, it is exactly my question. MDN said: "Because even though setTimeout was called with a delay of zero, it's placed on a queue and scheduled to run at the next opportunity, not immediately. Currently executing code must complete before functions on the queue are executed, the resulting execution order may not be as expected." (developer.mozilla.org/en-US/docs/Web/API/WindowTimers/…)

@Steffomio 2016-06-03 20:34:42

Sounds like angularJs framework problems. If so, you simply have to add the function instead the example-numbers to be executed.

@erdysson 2016-06-03 11:46:03

May be this kind of approach works for you

function foo(args) {
  return new Promise((resolve, reject) => {
      // your code based on args
      if (args.length) {
         console.log("processing", args[0]);
         resolve(args.slice(1, args.length));
      } else {
         reject("finished");
      }
  }).then(args => {
     return foo(args);
  }).catch(() => {
    console.log("Tasks completed...");
  });
}

foo([1, 2, 3 ,4, 5]);

Output should be something like :

processing 1
processing 2
processing 3
processing 4
processing 5
Tasks completed...

@Need4Steed 2016-06-03 15:25:25

This is good, but I really need to call setTimeout(f,0) to trigger the dirty checking

Related Questions

Sponsored Content

26 Answered Questions

[SOLVED] How can I pass a parameter to a setTimeout() callback?

27 Answered Questions

[SOLVED] How to measure time taken by a function to execute

32 Answered Questions

[SOLVED] How to execute a JavaScript function when I have its name as a string

  • 2008-12-11 15:47:10
  • Kieron
  • 491557 View
  • 994 Score
  • 32 Answer
  • Tags:   javascript

3 Answered Questions

[SOLVED] Unexpected behavior: Javascript, setTimeout(), and IIFE

Sponsored Content