Skip to content

Instantly share code, notes, and snippets.

@gbraad
Last active August 30, 2017 14:55
Show Gist options
  • Save gbraad/a4226d855d4f6e2e8d5a to your computer and use it in GitHub Desktop.
Save gbraad/a4226d855d4f6e2e8d5a to your computer and use it in GitHub Desktop.
Deferred JavaScript

Deferred

Don't ever use $.get or $.post. Instead use $.ajax and provide both a success-handler and an error-handler.

A deferred objects has two properties, it is a state object, and it takes care of callback management.

Upon creation of Deferred the state is 'pending'

	var def = $.Deferred();

When the call resolve is made, the state transitions to 'resolved'

	def.resolve();

The state can be inspected with:

	def.state();	// "resolved"

After a transition happened, the object has finished and remains in this state. Even if you would reject() it afterwards.

To make practical use of the object, we can define callbacks such as 'done', 'fail' and 'always' which function on either resolving or rejecting the deferred. Always will, as the name implies, always happen. this is comparable to a finally in a try-catch.

	var def = $.Deferred();

	def.done(function() {
		console.log("Resolved");
	});
	def.fail(function() {
		console.log("Rejected");
	});
	def.always(function() {
		console.log("Finally");
	});

	def.reject();
	//def.state();

Async functions!

	deferred.when()
		deals with subordinates 

	deferred.then()
		passes result on to interested functions.
		when object has been resolved already, they will execute immediately

Note: state can be read as boolean values using the following methods:

    def.isRejected();	// false
	def.isResolved();	// true

However, these functions are deprecated since 1.7 and also cause to not trigger then() which is attached after performing either of these calls. I suggest to not use them!

Ref: http://api.jquery.com/category/deferred-object/

Files

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<!--
<script src="//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.1.min.js"></script>
<script src="http://code.jquery.com/jquery-1.8.1.min.js"></script>
<script src="//webridge.local/scripts/jquery-1.8.1.js"></script>
-->
<script>document.log = function(text) { $('#console').append(text+'\n'); };</script>
<script>
$(function() {
var task = $.Deferred();
task.done(function(data) { // if(true) { .. }
document.log("Finished "+ data.answer);
});
task.fail(function(data) { // if(false) { .. }
document.log("Overtime " + data.answer);
});
task.always(function(data) { // (try-catch)-finally
document.log("Cleanup " + data.answer);
});
task.then(function(data) { // if(true) { [do] }
document.log("Make coffee " + data.answer);
});
document.log("Task is " + task.state());
task.resolve({answer: 42});
//task.reject(13);
task.then(function(data) { // do when resolved
document.log("Take a rest " + data.answer);
});
document.log("Task is " + task.state());
});
</script>
</head>
<body>
<section>
<h1>Output</h1>
<pre id="console"></pre>
</section>
</body>
</html>
jQuery.extend({
Deferred: function( func ) {
var tuples = [
// action, add listener, listener list, final state
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
],
state = "pending",
promise = {
state: function() {
return state;
},
always: function() {
deferred.done( arguments ).fail( arguments );
return this;
},
then: function( /* fnDone, fnFail, fnProgress */ ) {
var fns = arguments;
return jQuery.Deferred(function( newDefer ) {
jQuery.each( tuples, function( i, tuple ) {
var action = tuple[ 0 ],
fn = fns[ i ];
// deferred[ done | fail | progress ] for forwarding actions to newDefer
deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
function() {
var returned = fn.apply( this, arguments );
if ( returned && jQuery.isFunction( returned.promise ) ) {
returned.promise()
.done( newDefer.resolve )
.fail( newDefer.reject )
.progress( newDefer.notify );
} else {
newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
}
} :
newDefer[ action ]
);
});
fns = null;
}).promise();
},
// Get a promise for this deferred
// If obj is provided, the promise aspect is added to the object
promise: function( obj ) {
return typeof obj === "object" ? jQuery.extend( obj, promise ) : promise;
}
},
deferred = {};
// Keep pipe for back-compat
promise.pipe = promise.then;
// Add list-specific methods
jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// promise[ done | fail | progress ] = list.add
promise[ tuple[1] ] = list.add;
// Handle state
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// [ reject_list | resolve_list ].disable; progress_list.lock
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ] = list.fire
deferred[ tuple[0] ] = list.fire;
deferred[ tuple[0] + "With" ] = list.fireWith;
});
// Make the deferred a promise
promise.promise( deferred );
// Call given func if any
if ( func ) {
func.call( deferred, deferred );
}
// All done!
return deferred;
},
// Deferred helper
when: function( subordinate /* , ..., subordinateN */ ) {
var i = 0,
resolveValues = core_slice.call( arguments ),
length = resolveValues.length,
// the count of uncompleted subordinates
remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
// Update function for both resolve and progress values
updateFunc = function( i, contexts, values ) {
return function( value ) {
contexts[ i ] = this;
values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
if( values === progressValues ) {
deferred.notifyWith( contexts, values );
} else if ( !( --remaining ) ) {
deferred.resolveWith( contexts, values );
}
};
},
progressValues, progressContexts, resolveContexts;
// add listeners to Deferred subordinates; treat others as resolved
if ( length > 1 ) {
progressValues = new Array( length );
progressContexts = new Array( length );
resolveContexts = new Array( length );
for ( ; i < length; i++ ) {
if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
resolveValues[ i ].promise()
.done( updateFunc( i, resolveContexts, resolveValues ) )
.fail( deferred.reject )
.progress( updateFunc( i, progressContexts, progressValues ) );
} else {
--remaining;
}
}
}
// if we're not waiting on anything, resolve the master
if ( !remaining ) {
deferred.resolveWith( resolveContexts, resolveValues );
}
return deferred.promise();
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment