3

I have a long sequence of asynchronous functions to be executed sequentially. So I used promises to create promise queue. Unfortunately on Firefox 25.0.1 on Linux I'm getting too much recursion JavaScript error. It works fine on the same Firefox version on Mac, it works in chrome as well. But I need it working everywhere, including Linux. Can you propose better implementation of executePromiseQueueSync function?

//----------------------------------------------------------------------------------------- function executePromiseQueueSync(queue){ var seed = $.Deferred(); var acc = seed; for (var i = 0; i < queue.length; ++i ) { var promise = queue[i]; acc = acc.then(promise.funct.apply(null, promise.argmnt)); } seed.resolve(); return acc; } //----------------------------------------------------------------------------------------- function someTask(){ var dfd = new jQuery.Deferred(); dfd.notify(); dfd.resolve(); return dfd.promise(); } //----------------------------------------------------------------------------------------- $(function(){ var promisesQueue = [] for(var i = 0; i < 200; ++i){ promisesQueue.push({funct:someTask, argmnt:[]}); } executePromiseQueueSync(promisesQueue).then(function(){alert('done!');}); }); //----------------------------------------------------------------------------------------- 

JSFiddle example: http://jsfiddle.net/C2YN4/4/

What I have so far (and I'm very open to other propositions and corrections):

function executePromiseQueueSync(queue){ if (queue.length > TRESHOLD){ var i,j; var shortQueue = [] for (i=0,j=queue.length; i<j; i+=TRESHOLD) { temparray = queue.slice(i, i+TRESHOLD); shortQueue.push({funct:executePromiseQueueSync, argmnt:[temparray]}); } return executePromiseQueueSync(shortQueue); } var seed = $.Deferred(); var acc = seed; for (var i = 0; i < queue.length; ++i ) { var promise = queue[i]; acc = acc.then(promise.funct.apply(null, promise.argmnt)); } seed.resolve(); return acc; } 

So the basic idea is to make a promise tree instead of promise chain. This way we don't go so much deep into stack.

example: http://jsfiddle.net/fMBJK/1/

14
  • Why does promise.funct get called when it does not return a function? It looks like you are passing promises to .then(), which is invalid Commented Nov 28, 2013 at 14:20
  • Why are you using promises if someTask is synchronous? Commented Nov 28, 2013 at 14:21
  • @Bergi - you can remove promise from someTask() and problem will remain. Commented Nov 28, 2013 at 14:26
  • Is it required to use promises? Or do you just want to execute the chain in order while waiting for the previous function to finish? Commented Nov 28, 2013 at 14:27
  • @Bergi - Imagine you would like to execute some ajax calls in sequence. Is that example enough for you? Commented Nov 28, 2013 at 14:27

2 Answers 2

2

You can make use of jQuery.when ~ although it might be a little bit mad passing in over 200 arguments:

http://jsfiddle.net/6j6em/1/

The code from the fiddle

JavaScript with jQuery

var resolves = []; function log(msg){ $('#log').append('<div><small>' + msg + '</small></div>'); } function someTask(i){ var dfd = new $.Deferred(); log('created task '+i); resolves.push(function(){ log('resolved task '+i); dfd.resolve(); }); return dfd.promise(); } $(function(){ $('#resolve1').click(function(){ for ( var i=0; i<resolves.length; i++ ) { resolves[i](); } }); $('#resolve2').click(function(){ for ( var i=0; i<resolves.length; i++ ) { if ( i == 5 ) continue; resolves[i](); } }); $('#resolve3').click(function(){ resolves[5](); }); var i, queue = []; for(i=0; i<200; ++i){ queue.push(someTask(i)); } (jQuery.when.apply(jQuery, queue)) .done(function(){ log('all resolved!'); alert('all resolved!'); }) ; }); 

Markup for this example

<button id="resolve1">Resolve all</button> &nbsp;&nbsp; <button id="resolve2">Resolve (all but one)</button> <button id="resolve3">Resolve (the remaining one)</button> <div id="log"></div> 

CSS for this example

#log { border: 1px solid black; padding: 10px; width: 400px; height: 200px; overflow: auto; } 


Explanation

Much of the above code is just to illustrate, the key points are:

 var i, queue = []; for(i=0; i<200; ++i){ queue.push(someTask(i)); } (jQuery.when.apply(jQuery, queue)) .done(function(){ log('all resolved!'); alert('all resolved!'); }) ; 

The above generates an array of promise objects, and then using apply passes them to jQuery.when which then handles creating the correct structure to trigger a done callback once they have all completed. That is if you want this kind of behavior. If you want a system that will wait for each promise object to resolve in order, before triggering the next task, you'll need something different.


Update

In terms of firing tasks in sequence you need a different approach, something like this:

http://jsfiddle.net/6j6em/2/

function log(msg){ $('#log').append('<div><small>' + msg + '</small></div>'); } function someTask(i){ var dfd = new $.Deferred(); log(':: created task '+i); setTimeout(function(){ log(':: resolved task '+i); dfd.resolve(); },50); return dfd.promise(); } $(function(){ var Queue; Queue = function( items ){ this.items = items.slice(0); this.promise = $.Deferred(); }; Queue.prototype.next = function(){ log(':: next task triggered'); var q = this; q.lastItem = q.items.shift(); if ( q.lastItem ) { q.lastPromise = q.lastItem.func.apply( null, q.lastItem.args ); q.lastPromise.then(function(){ /// include a setTimeout 0 to avoid possible stack/recursion errors. setTimeout(function(){ q.next(); },0); }); } else { /// we are finished q.promise.resolve(); } }; Queue.prototype.run = function(){ this.next(); }; var i, items = []; for(i=0; i<200; ++i){ items.push({ func: someTask, args:[i] }); } var q = new Queue( items ); q.promise.done(function(){ log(':: done!'); alert('Done!'); }); q.run(); }); 

This builds a bespoke Queue object that keeps track of the list of promises, and after the first is successful, triggers the next. This code obviously requires error handling however.


update x2

You can't rely on progress for each promise because there is only one being triggered at any one time. You can add your own notification call out to the overall $.Deferred() object however.

var Queue; Queue = function( items ){ this.items = items.slice(0); this.promise = $.Deferred(); this.count = 0; }; Queue.prototype.next = function(){ log(':: next task triggered'); var q = this; q.lastItem = q.items.shift(); if ( q.lastItem ) { q.lastPromise = q.lastItem.func.apply( null, q.lastItem.args ); q.lastPromise.then(function(){ q.promise.notify(q.count++); q.next(); }); } else { q.promise.resolve(); } }; Queue.prototype.run = function(){ this.next(); }; var q = new Queue( items ); q.promise .done(function(){log(':: done!');}) .progress(function(p){log('::progress ' + p);}) ; q.run(); 
Sign up to request clarification or add additional context in comments.

5 Comments

@mnowotka ~ no it isn't, most often you use promises because you don't know the order in which things will occur. It wasn't clear from you question which was required. I'll need to update with the other approach.
@mnowotka ~ I've added an update. This code will fire the tasks in sequence.
Actually it seems to work even if you remove setTimeout. Nice!
@mnowotka ~ The problem is because you are essentially triggering one task/promise after the other, progress wont be trackable on each promise. You can enhance the code above to bring it back into play for the overall Queue object however ... see my edit.
I sorted it out. Everything works like a charm. Thank you very much!
1

FWIW... I'm a bit late, but here is my implementation of your queue requirement:

function RunnablePromise(func, args, thisArg) { var def = $.Deferred(), prm = def.promise(); prm.run = function () { func.apply(thisArg, args).done(def.resolve).fail(def.reject); return prm; }; return prm; } function PromiseQueue() { var q = [], overall = $.Deferred(), self = this; this.run = function () { var runnable = q.shift(); if (runnable) { overall.notify(q.length); runnable.run().done(self.run).fail(overall.reject); } else { overall.resolve(); } return overall.promise(); }; this.append = function (task, args, thisArg) { var runnable = new RunnablePromise(task, args, thisArg); q.push(runnable); return runnable; }; } 

Used like this:

var pq = new PromiseQueue(), i, success = function (i) { console.log("task done: " + i); }; for (i = 0; i < 200; i++) { // .append() returns the individual task's promise pq.append(someAsyncTask, [i]).done(success); } // .run() returns the entire queue's promise pq.run().progress(function (remain) { console.log("remaining: " + remain); }).done(function () { console.log("all done!"); }).fail(function () { console.log("error!"); }); 

http://jsfiddle.net/CxNDv/

2 Comments

Thanks for your solution, can you comment on it? Why is it better then the one accepted?
Well, for one, it is shorter. ;) I also think it's more readable; the concept being roughly the same. But ultimatlely: I had an answer in the works and was interrupted before I could finish, but didn't want to throw it away just because I was late.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.