Jump To …

deferred.js

superscore deferred.js 0.2.0
(c) 2012 David Souther
superscore is freely distributable under the MIT license.
For all details and documentation:
https://github.com/DavidSouther/superscore
(function(_){
	var flagsCache = {};

Convert String-formatted flags into Object-formatted ones and store in cache

	function createFlags( flags ) {
			var object = flagsCache[ flags ] = {},
					i, length;
			flags = flags.split( /\s+/ );
			for ( i = 0, length = flags.length; i < length; i++ ) {
					object[ flags[i] ] = true;
			}
			return object;
	}

Save references to some utilities

	var slice = Array.prototype.slice;

	_.Callbacks = function( flags ) {

Convert flags from String-formatted to Object-formatted (we check in cache first)

		flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};

		var // Actual callback list
			list = [],

Stack of fire calls for repeatable lists

			stack = [],

Last fire value (for non-forgettable lists)

			memory,

Flag to know if list was already fired

			fired,

Flag to know if list is currently firing

			firing,

First callback to fire (used internally by add and fireWith)

			firingStart,

End of the loop when firing

			firingLength,

Index of currently firing callback (modified by remove if needed)

			firingIndex,

Add one or several callbacks to the list

			add = function( args ) {
				var i,
					length,
					elem,
					type,
					actual;
				for ( i = 0, length = args.length; i < length; i++ ) {
					elem = args[ i ];
					if ( _.isArray(elem) ) {

Inspect recursively

						add( elem );
					} else if ( _.isFunction(elem) ) {

Add if not in unique mode and callback is not in

						if ( !flags.unique || !self.has( elem ) ) {
							list.push( elem );
						}
					}
				}
			},

Fire callbacks

			fire = function( context, args ) {
				args = args || [];
				memory = !flags.memory || [ context, args ];
				fired = true;
				firing = true;
				firingIndex = firingStart || 0;
				firingStart = 0;
				firingLength = list.length;
				for ( ; list && firingIndex < firingLength; firingIndex++ ) {
					if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
						memory = true; // Mark as halted
						break;
					}
				}
				firing = false;
				if ( list ) {
					if ( !flags.once ) {
						if ( stack && stack.length ) {
							memory = stack.shift();
							self.fireWith( memory[ 0 ], memory[ 1 ] );
						}
					} else if ( memory === true ) {
						self.disable();
					} else {
						list = [];
					}
				}
			},

Actual Callbacks object

			self = {

Add a callback or a collection of callbacks to the list

				add: function() {
					if ( list ) {
						var length = list.length;
						add( arguments );

Do we need to add the callbacks to the current firing batch?

						if ( firing ) {
							firingLength = list.length;

With memory, if we're not firing then we should call right away, unless previous firing was halted (stopOnFalse)

						} else if ( memory && memory !== true ) {
							firingStart = length;
							fire( memory[ 0 ], memory[ 1 ] );
						}
					}
					return this;
				},

Remove a callback from the list

				remove: function() {
					if ( list ) {
						var args = arguments,
							argIndex = 0,
							argLength = args.length;
						for ( ; argIndex < argLength ; argIndex++ ) {
							for ( var i = 0; i < list.length; i++ ) {
								if ( args[ argIndex ] === list[ i ] ) {

Handle firingIndex and firingLength

									if ( firing ) {
										if ( i <= firingLength ) {
											firingLength--;
											if ( i <= firingIndex ) {
												firingIndex--;
											}
										}
									}

Remove the element

									list.splice( i--, 1 );

If we have some unicity property then we only need to do this once

									if ( flags.unique ) {
										break;
									}
								}
							}
						}
					}
					return this;
				},

Control if a given callback is in the list

				has: function( fn ) {
					if ( list ) {
						var i = 0,
							length = list.length;
						for ( ; i < length; i++ ) {
							if ( fn === list[ i ] ) {
								return true;
							}
						}
					}
					return false;
				},

Remove all callbacks from the list

				empty: function() {
					list = [];
					return this;
				},

Have the list do nothing anymore

				disable: function() {
					list = stack = memory = undefined;
					return this;
				},

Is it disabled?

				disabled: function() {
					return !list;
				},

Lock the list in its current state

				lock: function() {
					stack = undefined;
					if ( !memory || memory === true ) {
						self.disable();
					}
					return this;
				},

Is it locked?

				locked: function() {
					return !stack;
				},

Call all callbacks with the given context and arguments

				fireWith: function( context, args ) {
					if ( stack ) {
						if ( firing ) {
							if ( !flags.once ) {
								stack.push( [ context, args ] );
							}
						} else if ( !( flags.once && memory ) ) {
							fire( context, args );
						}
					}
					return this;
				},

Call all the callbacks with the given arguments

				fire: function() {
					self.fireWith( this, arguments );
					return this;
				},

To know if the callbacks have already been called at least once

				fired: function() {
					return !!fired;
				}
			};

		return self;
	};

	_.Deferred = function( func ) {
		var doneList = _.Callbacks( "once memory" ),
			failList = _.Callbacks( "once memory" ),
			progressList = _.Callbacks( "memory" ),
			state = "pending",
			lists = {
					resolve: doneList,
					reject: failList,
					notify: progressList
			},
			promise = {
					done: doneList.add,
					fail: failList.add,
					progress: progressList.add,

					state: function() {
							return state;
					},

Deprecated

					isResolved: doneList.fired,
					isRejected: failList.fired,

					then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
							deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
							return this;
					},
					always: function() {
							deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
							return this;
					},
					pipe: function( fnDone, fnFail, fnProgress ) {
							return _.Deferred(function( newDefer ) {
									_.each( {
											done: [ fnDone, "resolve" ],
											fail: [ fnFail, "reject" ],
											progress: [ fnProgress, "notify" ]
									}, function( data, handler ) {
											var fn = data[ 0 ],
													action = data[ 1 ],
													returned;
											if ( _.isFunction( fn ) ) {
													deferred[ handler ](function() {
															returned = fn.apply( this, arguments );
															if ( returned && _.isFunction( returned.promise ) ) {
																	returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
															} else {
																	newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
															}
													});
											} else {
													deferred[ handler ]( newDefer[ action ] );
											}
									});
							}).promise();
					},

Get a promise for this deferred If obj is provided, the promise aspect is added to the object

					promise: function( obj ) {
							if ( !obj ) {
									obj = promise;
							} else {
									for ( var key in promise ) {
											obj[ key ] = promise[ key ];
									}
							}
							return obj;
					}
			},
			deferred = promise.promise({}),
			key;

			for ( key in lists ) {
					deferred[ key ] = lists[ key ].fire;
					deferred[ key + "With" ] = lists[ key ].fireWith;
			}

Handle state

			deferred.done( function() {
				state = "resolved";
			}, failList.disable, progressList.lock ).fail( function() {
				state = "rejected";
			}, doneList.disable, progressList.lock );

Call given func if any

			if ( func ) {
				func.call( deferred, deferred );
			}

All done!

			return deferred;
	};

Deferred helper

	_.when = function( firstParam ) {
		var args = slice.call( arguments, 0 ),
			i = 0,
			length = args.length,
			pValues = new Array( length ),
			count = length,
			pCount = length,
			deferred = length <= 1 && firstParam && _.isFunction( firstParam.promise ) ?
					firstParam :
					_.Deferred(),
			promise = deferred.promise();
		function resolveFunc( i ) {
			return function( value ) {
				args[ i ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
				if ( !( --count ) ) {
					deferred.resolveWith( deferred, args );
				}
			};
		}
		function progressFunc( i ) {
			return function( value ) {
				pValues[ i ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value;
				deferred.notifyWith( promise, pValues );
			};
		}
		if ( length > 1 ) {
			for ( ; i < length; i++ ) {
				if ( args[ i ] && args[ i ].promise && _.isFunction( args[ i ].promise ) ) {
					args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
				} else {
					--count;
				}
			}
			if ( !count ) {
				deferred.resolveWith( deferred, args );
			}
		} else if ( deferred !== firstParam ) {
			deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
		}
		return promise;
	};

}.call(this, _));