Files
galaxy/static/scripts/utils/ajax-queue.js
T

177 lines
5.7 KiB
JavaScript

define([
], function(){
//ASSUMES: jquery
//=============================================================================
/** @class AjaxQueue
* Class that allows queueing functions that return jQuery promises (such
* as ajax calls). Each function waits for the previous to complete before
* being called
*
* @constructor accepts a list of functions and automatically begins
* processing them
*/
function AjaxQueue( initialFunctions ){
//TODO: possibly rename to DeferredQueue
var self = this;
/** the main deferred for the entire queue - note: also sends notifications of progress */
self.deferred = jQuery.Deferred();
/** the queue array of functions */
self.queue = [];
/** cache the response from each deferred call - error or success */
self.responses = [];
/** total number of fn's to process */
self.numToProcess = 0;
/** is the queue processing/waiting for any calls to return? */
self.running = false;
self.init( initialFunctions || [] );
self.start();
return self;
}
/** add all fns in initialFunctions (if any) to the queue */
AjaxQueue.prototype.init = function init( initialFunctions ){
var self = this;
initialFunctions.forEach( function( fn ){
self.add( fn );
});
};
/** add a fn to the queue */
AjaxQueue.prototype.add = function add( fn ){
//console.debug( 'AjaxQueue.prototype.add:', fn );
var self = this,
index = this.queue.length;
this.numToProcess += 1;
this.queue.push( function(){
var fnIndex = index,
xhr = fn();
// if successful, notify using the deferred to allow tracking progress
xhr.done( function( response ){
self.deferred.notify({ curr: fnIndex, total: self.numToProcess, response: response });
});
// (regardless of previous error or success) if not last ajax call, shift and call the next
// if last fn, resolve deferred
xhr.always( function( response ){
self.responses.push( response );
if( self.queue.length ){
self.queue.shift()();
} else {
self.stop();
}
});
});
};
/** start processing the queue */
AjaxQueue.prototype.start = function start(){
if( this.queue.length ){
this.running = true;
this.queue.shift()();
}
};
/** stop the queue
* @param {boolean} causeFail cause an error/fail on the main deferred
* @param {String} msg message to send when rejecting the main deferred
*/
AjaxQueue.prototype.stop = function stop( causeFail, msg ){
//TODO: doesn't abort current call
this.running = false;
this.queue = [];
if( causeFail ){
//TODO: spliced args instead
this.deferred.reject( msg );
} else {
this.deferred.resolve( this.responses );
}
this.numToProcess = 0;
this.deferred = jQuery.Deferred();
return this;
};
// only a handful of the deferred interface for now - possible YAGNI
/** implement done from the jq deferred interface */
AjaxQueue.prototype.done = function done( fn ){
return this.deferred.done( fn );
};
/** implement fail from the jq deferred interface */
AjaxQueue.prototype.fail = function fail( fn ){
return this.deferred.fail( fn );
};
/** implement always from the jq deferred interface */
AjaxQueue.prototype.always = function always( fn ){
return this.deferred.always( fn );
};
/** implement progress from the jq deferred interface */
AjaxQueue.prototype.progress = function progress( fn ){
return this.deferred.progress( fn );
};
/** shortcut constructor / fire and forget
* @returns {Deferred} the queue's main deferred
*/
AjaxQueue.create = function create( initialFunctions ){
return new AjaxQeueue( initialFunctions ).deferred;
};
//=============================================================================
/** @class NamedAjaxQueue
* @augments AjaxQueue
* Allows associating a name with a deferring fn and prevents adding deferring
* fns if the name has already been used. Useful to prevent build up of duplicate
* async calls.
* Both the array initialFunctions sent to constructor and any added later with
* add() should be objects (NOT functions) of the form:
* { name: some unique id,
* fn: the deferring fn or ajax call }
*/
function NamedAjaxQueue( initialFunctions ){
var self = this;
self.names = {};
AjaxQueue.call( this, initialFunctions );
return self;
}
NamedAjaxQueue.prototype = new AjaxQueue();
NamedAjaxQueue.prototype.constructor = NamedAjaxQueue;
/** add the obj.fn to the queue if obj.name hasn't been used before */
NamedAjaxQueue.prototype.add = function add( obj ){
//console.debug( 'NamedAjaxQueue.adding:', obj )
//console.debug( 'NamedAjaxQueue.prototype.add:', obj );
if( !( obj.hasOwnProperty( 'name' ) && obj.hasOwnProperty( 'fn' ) ) ){
throw new Error( 'NamedAjaxQueue.add requires an object with both "name" and "fn": ' + JSON.stringify( obj ) );
}
if( this.names.hasOwnProperty( obj.name ) ){
//console.warn( 'name has been used:', obj.name );
return;
}
this.names[ obj.name ] = true;
//console.debug( '\t names: ', this.names )
AjaxQueue.prototype.add.call( this, obj.fn );
//console.debug( '\t queue: ', this.queue.length );
};
/** override to remove names */
NamedAjaxQueue.prototype.clear = function clear(){
this.names = {};
};
/** shortcut constructor / fire and forget
* @returns {Deferred} the queue's main deferred
*/
NamedAjaxQueue.create = function create( initialFunctions ){
return new NamedAjaxQueue( initialFunctions ).deferred;
};
//=============================================================================
return {
AjaxQueue : AjaxQueue,
NamedAjaxQueue : NamedAjaxQueue
};
});