from callbacks...

... to $.Deferred

... to $.Callbacks

Julian Aubourg
www.creative-area.net
jaubourg.net
@jaubourg

Deferreds: yay or nay?

BEFORE 1.5

"OMG! That's so 2010!"
var tmpl, data, count = 2;
function decount() {
	if ( ! --count ) {
		apply_template( tmpl, data );
	}
}
$.ajax({
	url: "template.php",
	success: function( t ) {
		tmpl = t;
		decount();
	}
});
$.ajax({
	url: "getMyId.php",
	data: { name: "Julian" },
	success: function( id ) {
		$.ajax({
			url: "data.php",
			data: { id: id },
			success: function( d ) {
				data = d;
				decount();
			}
		});
	}
});

1.5

"OMG! That's so January!"
var tmpl = $.ajax( "template.php" );
$.ajax( "getMyId.php", {
	data: { name: "Julian" },
	success: function( id ) {
		$.when(
			tmpl,
			$.ajax( "data.php", {
				data: { id: id }
			});
		).done(function( t, d ) {
			apply_template( t[ 0 ], d[ 0  ] );
		});
	}
});

1.6

"OMG! I haven't upgraded yet!"
$.when(
	$.ajax( "template.php" ),
	$.ajax( "getMyId.php", { data: { name: "Julian" } } ).pipe(function( id ) {
		return $.ajax( "data.php", {
			data: { id: id }
		});
	})
).done(function( t, d ) {
	apply_template( t[ 0 ], d[ 0 ] );
});

1.7

"OMG! Is that even released?"
$.when(
	$.ajax( "template.php" ),
	$.ajax( "getMyId.php", { data: { name: "Julian" } } ).pipe(function( id ) {
		return $.ajax( "data.php", {
			data: { id: id }
		});
	})
).done(function( t, d ) {
	apply_template( t[ 0 ], d[ 0 ] );
});

because pipe is awesome enough

pipe as a simple filter


		var defer = $.Deferred();
		var filtered = defer.pipe(function( company ) {
			return company + " is always late";
		});
		filtered.done( output );
		defer.resolve( "American Airlines" );

pipe as a chaining tool


		var defer = $.Deferred();
		var filtered = defer.pipe(function( company ) {
			return $.Deferred(function( defer ) {
				setTimeout( function() {
					defer.resolve( company + " is still late" );
				}, 1000 );
			});
		});
		filtered.done( output );
		defer.resolve( "American Airlines" );

pipe as a redirection tool


		var defer = $.Deferred();
		var filtered = defer.pipe(function( company ) {
			return $.Deferred(function( defer ) {
				setTimeout( function() {
					defer.reject( company + " is late, damnit!" );
				}, 1000 );
			});
		});
		filtered.fail( output );
		defer.resolve( "American Airlines" );

So pipe can be used as...

a resolution/rejection value filter,
a chaining system,
a deferred "rerouter".

Questions anyone?

OK, what's new in 1.7 then?


progress callbacks for Deferreds
$.Callbacks (OMG! $.What?!?)

Progress


a means to notify changes in an asynchronous task's state
well before it is fully resolved or rejected.

Some kind of repeatable resolve/done,
resolve => notify
done => progress

notify and progress


		var defer = $.Deferred().done( function() {
			output( "done!" );
		}).progress( output );
		
		defer.notify( "hello" );
		defer.notify( "world" );
		
		defer.progress(function( data ) {
			output( "other handler: " + data );
		});
		
		defer.notify( "two" );
		
		defer.resolve();
		
		defer.notify( "donotwant" );
		
		defer.progress( output );

cool! then handles progress...


		var defer = $.Deferred().then( null, null, output );

		defer.notify( "hello" );
		defer.notify( "world" );

yeah! pipe handles progress too!


		var defer = $.Deferred().progress( output );
		var halfDone = defer.pipe( null, null, function( value ) {
			return value == 50 ?
				$.Deferred().resolve() :
				value ;
		});
		
		halfDone.progress(function( value ) {
			output( "half done: " + value );
		}).done(function() {
			output( "half done!" );
		});

		defer.notify( 0 );
		defer.notify( 25 );
		defer.notify( 50 );
		defer.notify( 75 );
		defer.notify( 100 );

OMG! when handles progress!!!


		var defer1 = $.Deferred();
		var defer2 = $.Deferred();
		
		$.when( defer1, defer2 ).progress(function( value1, value2 ) {
			output( "v1: " + value1 + ", v2: " + value2 );
		});

		defer1.notify( 0 );
		defer2.notify( 50 );
		defer1.notify( 100 );
		defer2.notify( 100 );

... and we have isProgressing to boot ;)


		var defer = $.Deferred();
		
		output( defer.isProgressing() );

		defer.notify( "see?" );

		output( defer.isProgressing() );

		defer.resolve();
		
		output( defer.isProgressing() );

So progress...

is repeatable,
acts like a re-resolvable deferred of old,
is locked after resolution/rejection,
yet can still be requested then!

Questions anyone?

$.Callbacks

$.Callbacks is...

a generic multi-purpose callbacks list object,
the building block behind $.Deferred (resolve, reject and notify),
designed to be the building block of whatever *you* may think of next.

Default

		var cbs = $.Callbacks();
		var fn = function( value ) {
			output( "fn: " + value );
			return false;
		};
		
		cbs.add( fn );	
		cbs.fire( "hello" );
		cbs.add( output );
		cbs.fire( "world" );
		cbs.remove( output );
		cbs.fire( "end" );

Memory

		var cbs = $.Callbacks( "memory" );
		var fn = function( value ) {
			output( "fn: " + value );
			return false;
		};
		
		cbs.add( fn );	
		cbs.fire( "hello" );
		cbs.add( output );
		cbs.fire( "world" );
		cbs.remove( output );
		cbs.fire( "end" );

Once

		var cbs = $.Callbacks( "once" );
		var fn = function( value ) {
			output( "fn: " + value );
			return false;
		};
		
		cbs.add( fn );	
		cbs.fire( "hello" );
		cbs.add( output );
		cbs.fire( "world" );
		cbs.remove( output );
		cbs.fire( "end" );

stopOnFalse

		var cbs = $.Callbacks( "stopOnFalse" );
		var fn = function( value ) {
			output( "fn: " + value );
			return false;
		};
		
		cbs.add( fn );	
		cbs.fire( "hello" );
		cbs.add( output );
		cbs.fire( "world" );
		cbs.remove( output );
		cbs.fire( "end" );

Just like Deferred.resolve

		var cbs = $.Callbacks( "once memory" );
		var fn = function( value ) {
			output( "fn: " + value );
			return false;
		};
		
		cbs.add( fn );	
		cbs.fire( "hello" );
		cbs.add( output );
		cbs.fire( "world" );
		cbs.remove( output );
		cbs.fire( "end" );

$.Callbacks, the methods:

add,
remove,
has,
empty,
disable(d),
lock(ed),
fireWith,
fire(d)

OMG! OMG!
ME WANTS SOME $.CALLBACKS
EVERYWHERE!!!
ALL THE TIME!!!
FOR GREAT JUSTICE!!!

Imagine what's below on a bigger scale...

		var cb1 = $.Callbacks( "once memory" );
		var cb2 = $.Callbacks();
		cb1.add( cb2.fire );
		cb1.fire( "DUNNO WHAT I R DOIN!" );
... scared already?

Let's make it a bit clearer...

		var defer = $.Deferred();
		var cb2 = $.Callbacks();
		defer.done( cb2.fire );
		defer.resolve( "OH! ME CAN HALF READ NOW!" );
... yeah... half :/

$.Callbacks methods are detachable

Ben Nadel would say "lexically bound"

		var cbs = $.Callbacks();
		var add = cbs.add;
		var fire = cbs.fire;
		add( output );
		fire( "see?" );
=> used heavily in $.Deferred
=> you can and *should* use it too!

pub/sub the $.Callbacks way?

var topics = {};

jQuery.Topic = function( id ) {
	var callbacks,
		method,
		topic = id && topics[ id ];
	if ( !topic ) {
		callbacks = jQuery.Callbacks();
		topic = {
			publish: callbacks.fire,
			subscribe: callbacks.add,
			unsubscribe: callbacks.remove
		};
		if ( id ) {
			topics[ id ] = topic;
		}
	}
	return topic;
};

You'd use it like this:

$.Topic( "myTopic" ).subscribe( output );

// Then in another module far far away

$.Topic( "myTopic" ).publish( "hihihi" );

Now, remember this?

		var cb1 = $.Callbacks( "once memory" );
		var cb2 = $.Callbacks();
		cb1.add( cb2.fire );
		cb1.fire( "DUNNO WHAT I R DOIN!" );

*Shazzam*!

		var defer = $.Deferred();
		var topic = $.Topic( "myGlobalTopic" );
		defer.done( topic.publish );
		defer.resolve( "OMG! PUBLISH!" );
... and it finally makes sense!
jaubourg