Based on the answer to my previous question on Stack Overflow, I put together the following Queue class. I realize there are already libraries out there to do this. However, I wanted to actually understand the concept and implementing it yourself really drives the knowledge home (it also provides much more fun and a greater sense of accomplishment!).
But I DO want to make sure I've done this in the best way. Please see if it has any obvious issues or lacks any necessary features. A few possible improvements:
Allow the list of functions passed to the constructor to be separate arguemnts, rather than an array of objects, by converting the
arguments
object into an array.One potential problem with this is if, in answer to #6 below, you guys feel the constructor should accept a context to be applied as
this
when calling the queued functions. To my mind, it seems more intuitive to havevar Queue = function( arrayOfFuncs, context ) {
than this:var Queue = function() { var list = Array.prototype.slice.call( arguments ), context = this, self = this; if (typeof list[0] !== 'function') { context = list.shift(); // Context would have to be the first argument passed in } // ... };
Similarly deal with
returnValue
as an array, so that a variable number of arguments could be passed to the callback.- Adding onComplete or onError handlers, allowing easier access to the final return value.
- Add more helper functions like 'delay'. (For instance, a 'timer' method which handles an array, sending the items in the array to a specified processing function in batches. I.e. a fancy way to perform common setInterval patterns.)
- Perhaps methods to add or delete items from the queue?
- What should be passed as
this
to each function in the queue? Right now I am passing the queue itself, to allow the functions access tothis.paused
if they want to pause an interval (i.e. keepreturn
ing from that interval until no longer paused)
What else should a good queue have?
var Queue = function(queue) { var list = queue, self = this; // Use scope to allow Queue.next() to reference 'this' this.paused = false; // Ensures Queue.start() doesn't skip a currently running interval when // resuming from pause this.running = false; // Allows clearing of timeout / interval this.current = null; this.returnValue; this.next = function(returnValue) { self.running = false; if (list.length === 0) { self.returnValue = returnValue; return; } // Grab next function in queue and add returnvalue to args if needed var next = list.shift(); if (returnValue) next.args.push( returnValue ); if (self.paused) { // Stick the function back at beginning of queue to await restart list.unshift( next ); return; } else { self.running = true; // Call next function, applying the Queue as 'this' self.current = next.fn.apply(self, next.args); } }; for (var i = 0; i < list.length; i++) { // Ensure there is an args array, then add Queue.next() as callback if (!list[i].args) list[i].args = []; list[i].args.push( this.next ); // Allows easier method of adding delays and pauses to queue if (typeof list[i].fn === 'string') { list[i].fn = this[ list[i].fn ]; } } }; Queue.prototype = { delay : function(time, callback, returnValue) { // Allows leaving out the args array and using default 1000ms // Remaps arguments if it has been left off if (typeof callback !== 'function') { returnValue = callback; callback = time; time = 1000; } return setTimeout( function() { callback( returnValue ); }, +time ); }, pause : function(callback, returnValue) { this.paused = true; // Allows Queue.pause() to be called either in the queue or while the // queue is running, in which case no callback need be specified if (callback) { callback( returnValue ); } }, advance : function() { // Executes queue items one at a time // Clear via both types, to ensure it's actually cleared clearInterval( this.current ); clearTimeout( this.current ); this.paused = false; this.next(); this.paused = true; }, start : function() { this.paused = false; // As stated earlier, a currently running interval will be completed // rather than skipping to the next queued function if (!this.running) this.next(); } }; function $(id) {return document.getElementById(id);} // Helper function type(from, to, callback, returnValue) { // Example async function // Shows that the return value from the first call of this function // carries through; notice 'returnValue' comes after 'callback' console.log(returnValue); var chars = from.value.split(''), self = this, interval = setInterval( function() { // If the following line is commented, it allows us to use Queue.advance() // If uncommented, we can use Queue.pause() and .start() // to pause and resume interval /* if (self.paused) return; */ to.value += chars.shift(); if (chars.length === 0) { clearInterval(interval); // Return a value by passing as param to callback callback(to.value); } }, 35); // Return an ID to allow Queue.advance() to clear the interval or timeout return interval; } window.testQueue = new Queue([ { fn: type, args: [$('textarea1'), $('textarea2')] }, { fn: 'delay', args: [3000] }, // Try with or without the args array { fn: 'pause' }, { fn: type, args: [$('textarea3'), $('textarea4')] } ]); // Start it, or use Queue.advance() from the console //testQueue.start(); // When Queue.pause() is reached in the queue, manually call Queue.start()