3
\$\begingroup\$

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:

  1. 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 have var 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
        }
        // ...
    };
    
  2. Similarly deal with returnValue as an array, so that a variable number of arguments could be passed to the callback.

  3. Adding onComplete or onError handlers, allowing easier access to the final return value.
  4. 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.)
  5. Perhaps methods to add or delete items from the queue?
  6. 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 to this.paused if they want to pause an interval (i.e. keep returning 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()
\$\endgroup\$

migrated from stackoverflow.com Aug 27 '13 at 11:12

This question came from our site for professional and enthusiast programmers.

2
\$\begingroup\$

From a once over,

  • I would check for if (self.paused) { at the very beginning, otherwise you might keep pushing returnValue into the args
  • I would not assign an anonymous function herre: this.next = function(returnValue) { I would go for this.next = function next(returnValue) { this will make life easier when looking at stacktraces
  • It does not make sense to maintain both self.running and self.paused since self.running will always be !self.paused
  • I would write if (!list[i].args) list[i].args = []; as
    list[i].args = list[i].args || []
  • JsHint has nothing to report

On the whole though, I am not sure I would use this library. I think I would go hunt for an existing queue library.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.