DEFERREDS

Putting Laziness to Work

Dan Heberden

danheberden.com
@danheberden
Use left and right arrow keys to navigate slides

this only runs/works right in chrome

Who you callin' lazy?!

Lazy Road Demotivational Poster

THE PROBLEM

A typical callback

						$.ajax({
							url: "server/get-data/",
							success: function( data ) {
								callMeBack( data );
							}
						});
				

happens more than you think

						function goGetIt() {
						
						  var whatGotReturned;
					
							$.ajax({
								url: "server/get-data/",
								success: function( data ) {
									whatGotReturned = data;
								}
							});
						
							return whatGotReturned;
						}
					
						goGetIt(); // returns undefined
					

What ends up having to happen

						function goGetIt( callMeBack ) {
							$.ajax({
								url: "server/get-data/",
								success: function( data ) {
									callMeBack( data );
								}
							});
						}
					
						goGetIt( function( data ) {
							// handle data
						}); 
					

sounds messy

decoupling hell

services have to worry about calling back

handling failures becomes a hassle

messy and hard to manage

we want something that...

manages our callbacks
handles successes and failures
can be passed around
has a consistent api

meet $.Deferred

is an object that

Holds success callbacks

Holds failure callbacks

Can call call either bucket
of callbacks

pieces of a deferred

done

Accepts any number of functions to call when resolved

fail

Accepts any humber of functions to call when rejected

then

Convenience method that takes one done and one fail callback

resolve ( and resolveWith )

Determine if the deferred has been rejected

The 'With' variants accept a context as the first arg

reject ( and rejectWith )

Determine if the deferred has been resolved

promise

more on this later :)

Creating a deferred

					// create a new deferred
					var dfd = $.Deferred();

				  dfd.done( function( data ) {
				  	output( 'done!' + data );
				  });

				  dfd.resolve( 'yay!' );
				

#fail

					// create a new deferred
					var dfd = $.Deferred();

				  dfd.fail( function( data ) {
				  	output( 'broked!' + data );
				  });

				  dfd.reject( 'oh noes!' );
				

Promises Promises

promise me

					function doSomethingAsynchronous() {
						var dfd = $.Deferred();
					
						setTimeout( function() {
							dfd.resolve( 'did it!' )
						}, 4000 );
					
						return dfd.promise();
					}							
				
					doSomethingAsynchronous()
						.done( function( data ) {
							output( 'we '+ data );
						});
				

a promise is forever (almost)

done

Accepts any number of functions to call when resolved

fail

Accepts any humber of functions to call when rejected

then

Convenience method that takes one done and one fail callback

isRejected

Determine if the deferred has been rejected

isResolved

Determine if the deferred has been resolved

promise

Another promise

Chaining goodness

				var didIt = function() {
							output( 'did it!' );
						},
						failed = function() {
							output( 'failed!' );
						}				
				doSomethingAsynchronous()
					.then( didIt, failed )
					.done( didIt )
					.fail( failed );
			

Promises/A

then( fulfilled, error, progress )

Requires a new deffered be returned to be able to call .then again

You can look it up if you wanna, but trust me: we did a good job

Edit: Question from #JQCON

If a deferred has already been resolved, and you attach a function to .done ( or similarly with rejected and .fail ) the function you attach will be called immediately (since it's already been resolved )

so now what?

We can create a deferred object that holds our callbacks

We can return, pass around, store, etc a public interface for the deferred: a promise

We can easily resolve or reject that deferred

Resolved or rejected data is passed to the callbacks no matter where they were assigned

$.ajax

as asynchronous

as it gets

$.ajax returns a promise

				$.ajax({
					url: 'srv/echo',
					data: { echo: "stuff",
					 				delay: 1,
					 				error: 1}
				}).done( 
					function( data, status, jqXHR ) {
						output( data );
					}
				);
			

nostalgia included

.success

Works just like .done

.error

Works just like .fail

.complete

Is called when complete but 1st param is the jqXHR

SO WHAT

				$.ajax({
					url: '/srv/echo',
					data: { echo: "stuff" },
					success: function( data ) {
						output( data );
					}
				});
			

still works, right?

Can your callback do this?

				function getUserName() {
					return $.ajax({
						url: 'srv/echo', 
						data: { random: 1 }
				  });
				}
			
				getUserName().done( function( name ) {
					output( name );
				});
			

Being Lazy

$.when

One utility method to rule them all

$.when magic

take any # of arguments and wraps them in a deferred

it even accepts promises!
						function aName() {
							return $.get( 'srv/echo', { random:1 });
						}

						$.when( aName(), "JQ", aName(), { a: "eh?" } )
							.done( function( a1, txt, a2, obj ) {
								output( a1[ 0 ] );
								output( txt );
								output( a2[ 0 ] );
								output( obj );
							});
					
				function aName() {
					return $.get( 'srv/echo', { random:1 });
				}
		
				function customDfd() {
					var dfd = $.Deferred();
					setTimeout( function() {
						dfd.resolve( 'all done!' );
					}, 4000 );
					return dfd.promise();
				}

				$.when( aName(), customDfd() )
					.done( function( name, custom ) {
						output( name[ 0 ] );
					});
			

cache anyone?

				var getName = (function() {
					
						var cache; // private cache
					
						// gets assigned to getName
						return function() {
					
							// return the cache if it's valid or
							return cache || $.ajax({
									url: 'srv/echo', 
									data: { random: 1 },
									success: function( data ) {
										cache = data;
									}
								});								
						};
					})();				
				
				var getName = (function() {
					
						var cache = false; // private cache
					
						// gets assigned to getName
						return function() {
					
							// return the cache if it's valid or
							return cache ? cache : $.ajax({
									url: 'srv/echo', 
									data: { random: 1 },
									success: function( data ) {
										cache = data;
									}
								}) ;								
						};
					})();
				
					$.when( getName() ).done( 
						function( name ) {
						
							output( name );
						
							$.when( getName() ).done( 
								function( name ) {
									output( name );
								});
							
						});				
				

omg 1.6

$.fn.animate's callback

				$( 'div.someClass' ).animate( {
						left: "+=100"
					}, {
						duration:1000,
						complete: function() {
							// gets called every time
						}
				  });
			

i $.fn.promise

makes a jquery collection observable

works with all queue'd actions

				$( '#bounceDemo div' ).animate({
						bottom: "0"
					},{
						duration: 1700,
						easing: "easeOutBounce"
					}).promise()
						.done( function() {
							output('all done');
						});
			
				$( '#bounceDemo div' ).each( function() {
					$(this).css('bottom', ~~(Math.random() * 5) + 4 + "em" );
				});				
			

Works with $.when too

					$.when( 
						$( '#bounceDemo2 div' ).animate({
									bottom: "0"
							},{
									duration: 1700,
									easing: "easeOutBounce"
						  })
				 ).done( function() {
						output('all done');
				 });
			
				$( '#bounceDemo2 div' ).each( function() {
					$(this).css('bottom', ~~(Math.random() * 5) + 4 + "em" );
				});				
			

cooler than that

				$.fn.drop = function( dur ) {
					return this.animate({
							bottom: 0
						}, {
							duration: dur || 1700,
							easing: "easeOutBounce"
						});
				}
			
				var $balls = $( '#bounceDemo3 div' ),
						a = $balls.eq(0).drop( 500 ),
						b = $balls.eq(1).drop( 2000 ),
						c = $balls.eq(2).drop( 4000 );
				$.when( a, b, c )
					.done( function() {
						output( 'lol the balls dropped' );
					});
			
				$( '#bounceDemo3 div' ).each( function() {
					$(this).css('bottom', ~~(Math.random() * 5) + 4 + "em" );
				});				
			

so what if

				var $balls = $( '#bounceDemo4 div' ),
						$a = $balls.eq(0).drop( 500 ),
						$b = $balls.eq(1).drop( 2000 ),
						$c = $balls.eq(2).drop( 4000 );
					
				setTimeout( function() {
					$c.stop();
				},1000 );
				$.when( $a, $b, $c )
					.done( function() {
						output( 'lol the balls dropped' );
					});
			
				$( '#bounceDemo4 div' ).each( function() {
					$(this).css('bottom', ~~(Math.random() * 5) + 4 + "em" );
				});				
			

what your callback sees

					var $balls = $( '#bounceDemo4 div' ),
							$a = $balls.eq(0).drop( 500 ),
							$b = $balls.eq(1).drop( 2000 ),
							$c = $balls.eq(2).drop( 4000 );
						
					$.when( $a, $b, $c )
						.done( function( a, b, c ) {
							// a is $a
							// b is $b
							// c is $c
						});
				

missing $.ajax already

					function aName( delay ) {
						return $.get( 'srv/echo', { random:1 });
					}
			
					var $balls = $( '#bounceDemo6 div' ),
							$a = $balls.eq(0).drop( 500 ),
							$b = $balls.eq(1).drop( 2000 ),
							$c = $balls.eq(2).drop( 4000 ),
							$name = $.get( 'srv/echo', {
														  random:1,
														 	delay: 5 });
													
					$.when( $a, $b, $c, $name )
						.done( function( a, b, c, nameReq ) {
							output( nameReq[ 0 ] );
						});
				
					$( '#bounceDemo6 div' ).each( function() {
						$(this).css('bottom', ~~(Math.random() * 5) + 4 + "em" );
					});				
				

works with helpers too

				var $balls = $( '#bounceDemo7 div' );
			
				$.when( $balls.fadeOut( 2000 ) )
					.done( function() {
						output( 'done fading out' );
					});
			
				$( '#bounceDemo7 div' ).each( function() {
					$(this).fadeIn(0);
				});				
			

.always

Wanna run the same function regardless if the deferred was resolved or rejected?

				var allDone = function() {}
			
				.always( allDone );
			
				// is the same as 
			
				.then( allDone, allDone );
			

.pipe - asynchronous inception

			var promise = $.ajax({
					url: 'srv/echo',
					data: { echo: "jQuery" }
				})
				.pipe( function( data ) {
					// return a new promise that gets piped
					return $.ajax({
						url: 'srv/echo',
						data: { echo: data + data + data}
					});
				});
			
			$.when( promise ).done( function( data ){
				output( data );
			});
		

putting deferreds to work

i.e. being lazy

our service

				function getUser( userID ) {
					return $.get( 'srv/test/user' , {
						id: userID
					});
				}
			
				$.when( getUser(3) )
					.done( function( data ) {
						output( data );
					});
			

our service unit testable

				function getUser( userID ) {					
					return { name: "Joe", email: "joe@nowhere.com"}
				}
			
				$.when( getUser(3) )
					.done( function( data ) {
						output( data );
					});
			

when would I use this?

templates

user takes action

template needs to be loaded if it isn't cached

data needs to be loaded

render template

got get the template

				(function() {
					var cache = {};
					app.getTemplate = function( tmpl ) {
						return cache[ tmpl ] || $.ajax({ 
							url: '/go/get/template/',
							data: { template: tmpl },
							success: function( data ) {
								cache[ tmpl ] = data;
							}
						});
					}					
				})();
			

go get the user

						(function() {
							app.getUserData = function( uID ) {
								return $.ajax({ 
									url: '/go/get/user/',
									data: { id: uID }
								});
							}					
						})();
					

omg it's easy now

				$.when( app.getTmpl( 'usrEdit' ),
					      app.getUserData( 14 ) )
					.done( function( tmplData, user ){
					
					  // render the template with
					  // the template and data
						$( tmplData ).tmpl( user )
							.appendTo( '#userDisplay' );
					});
			
				$.when( "talk" )
					.done( function() {
						output( "questions?" );
					})