Need4Steed Need4Steed - 6 months ago 21
Javascript Question

How to enforce the execution order of the tasks queued by setTimeout with 0 delay

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

Answer

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.