Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

stop procrastinating,

start $.Deferring!

http://tinyurl.com/nrhckto

Julian Aubourg

stuff I do

contact

goal of this talk

  • Learn about Promises
  • Rethink what you think you know about them
  • Learn about
    • $.when
    • $.Deferred
    • then()

Oh and I gist-wrapped it for you (take that hipsters!)

warning

THIS TALK IS NOT SEQUENTIAL

So sit back, relax, think asynchronously

ENJOY THE RIDE

Promise

  • a means to observe a one-shot task
    • an ajax request
    • an upload
    • an animation
    • solving an equation
    • ...
  • two possible outcomes:
    • positive: true / success / resolution
    • negative: false / error / rejection
  • (can notify progress along the way)

It's an Interface:

  • state() -- "pending", "resolved", "rejected"
  • done()
  • fail()
  • always()
  • progress()
  • then() -- aka pipe()
  • promise() -- returns itself (WAAAAAT?!?)

The jqXHR object implements it:

var jqXHR = $.ajax( options );

jqXHR.done( function() {
	console.log( "SUCCESS" );
} );

jqXHR.fail( function() {
	console.log( "ERROR" );
} );

You can add callbacks at any time.

This is where the AWESOMENESS strikes btw

var jqXHR = $.ajax( options );

// before the request returns
jqXHR.done( function() {
	console.log( "the request just returned" );
} );

// after the request returned
jqXHR.done( function() {
	console.log( "called immediately" );
} );

Groovy!

then()

(& the boolean metaphor)

var request = $.ajax( options );

request.then( function() {
	console.log( "SUCCESS" );
}, function() {
	console.log( "ERROR" );
} );

The word "then" is a dead giveaway:

  • the promise is kind of an async boolean
  • "then" is kind of an async conditional
var request = $.sjax( options );

if ( request.resolved() ) {
	console.log( "SUCCESS" );
} else {
	console.log( "ERROR" );
}

So done() and fail() are also conditionals?

request.done(
	success
);

request.fail(
	error
);
if ( request.resolved() ) {
	success();
}

if ( !request.resolved() ) {
	error();
}

$.when()

  • Promise => boolean
  • then(), done(), fail() => if/then/else, if, if not
  • $.when() => logical AND
var template = $.ajax( "template" );
var data = $.ajax( "data" );

$.when( template, data ).then( function() {
	console.log( "template + data available" );
} );

is roughly the same as

var template = $.sjax( "template" );
var data = $.sjax( "data" );

if ( template.resolved() && data.resolved() ) {
	console.log( "we got both template and data" );
}

Notable differences:

  • pure parallelism, no critical path, no sequence
  • $.when() fails as soon as one argument fails
$.when( pending_1, rejected, pending_2 ).fail( function() {
	// called immediately
} );

Observable

To mark an object as observable:

  • give it a promise() method
  • have this method return a promise (duh!)

OMG, a Promise is observable through itself!

promise.promise() === promise; // true

Why use a method?

  • dynamic behaviour
  • one entry point vs the entire interface

See jQuery collections:

var $div = $( "div" );

$div.done( fn ); // errors
$div.fail( fn ); // errors
// etc

// But the following works
$div.promise().done( function() {
	console.log( "all animations stopped" );
} );

And also on $.ready() (WAAAAAT?!?)

$.ready.promise(); // exists!

$.when() calls this internally!

$.when(

	$.ajax( "data" ),
	$.ajax( "template" ),
	$.ready

).done( function() {

	// populate template and insert it into the DOM

} );

Deferred

  • implements the Promise interface
  • plus methods to manage state and callbacks
    • resolve()
    • reject()
    • notify()

$.Deferred():

  • don't need no new
  • can take an init function (use it! use it! use it!)
function timeout( time ) {
	return $.Deferred( function( defer ) {
		setTimeout( defer.resolve, time );
	} );
}

// ...

var afterTwentySecs = timeout( 20000 );

afterTwentySecs.done( function() {
	console.log( "at least 20 seconds later" );
} );

// ... and 50 seconds later...

afterTwentySecs.done( function() {
	console.log( "immediately" );
} );

But wait:

var afterTwentySecs = timeout( 20000 );

afterTwentySecs.done( function() {
	console.log( "20 seconds later, right?" );
} );

afterTwentySecs.resolve(); // Guess not :(

Be safe, protect yourself, always use (return) a promise:

function timeout( time ) {
	return $.Deferred( function( defer ) {
		setTimeout( defer.resolve, time );
	} ).promise();
}

timeout( 20000 ).resolve(); // throws

It's our fault really, a Deferred:

  • should be Observable
  • shouldn't be a Promise

Time to break the web again!

Let's catch our breath

  • some objects are Observable through a Promise
  • a Promise is an Interface
  • a Promise is a tri-state boolean
  • then(), done() and fail() are conditionals
  • $.when() is a logical AND
  • $.Deferred() can be used to:
    • create a Promise
    • control its state

How about some...

implementation details

chaining().chaining().chaining()

promise.done( fn1 ).fail( fn2 ).always( fn3 ) === promise; // true

detachable methods

var defer = Deferred();
var resolve = defer.resolve;

resolve( "hello" ); // does NOT throw

defer.done( function( string ) {
	// called
	string === "hello"; // true
} );

simplify your life:

// avoid
myFunctionWithACallback( function( arg ) {
	defer.resolve( arg );
} );

// rather
myFunctionWithACallback( defer.resolve );

our methods are safe

var defer = Deferred();

defer.done( function( number ) {
	console.log( "called with " + number );
} );

defer.resolve( 1 );
defer.resolve( 2 );

$.when() with non-Observables

$.when( "omg a string" ).done( function( string ) {
	string === "omg a string"; // true
} );

Works when combined too

$.when( 1, Deferred().resolve( 2 ), 3 ).done( function( a, b, c ) {
	a === 1; // true
	b === 2; // true
	c === 3; // true
} );

multi-values and $.when()

var defer1 = $.Deferred().resolve( 1, 2 );
var defer2 = $.Deferred().resolve( 3, 4 )

$.when( defer1, defer2 ).done( function( a, b ) {
	// a => [ 1, 2 ]
	// b => [ 3, 4 ]
} );

get more advanced with then()

then() returns a new Promise!

defer.then().promise() !== defer.promise();

you can filter values

Deferred().resolve( 3 ).then( function( number ) {

	return number * 2;

} ).done( function( newNumber ) {

	newNumber === 6; // true

} );

WYRIWYG: what you return is what you get

Deferred().resolve( 3 ).then( function( number ) {

	// omg procrastination

} ).done( function( newNumber ) {

	newNumber === undefined; // true

} );

you can chain tasks

timeout( 2000 ).then( function() {

	return timeout( 3000 );

} ).done( function() {

	// 5 seconds later!

} );

you can combine

timeout( 2000 ).then( function() {

	return timeout( 3000 );

} ).then( function() {

	return "whatever";

} ).done( function( string ) {

	// 5 seconds later!
	string === "whatever";

} );

even inception-style

timeout( 2000 ).then( function() {

	return timeout( 3000 ).then( function() {

		return "whatever";

	} );

} ).done( function( string ) {

	// 5 seconds later!
	string === "whatever";

} );

you can switch states

Deferred().reject( "error" ).then( null, function() {

	return Deferred().resolve( "whatever" );

} ).done( function( string ) {

	string === "whatever"; // true

} );

you can even catch exceptions when and only when necessary:

Deferred().resolve( 0 ).then( function( number ) {

	try {

		// what could possibly go wrong?
		return 1 / number;

	} catch( e ) {

		return Deferred().reject( e );

	}

} );

then() is expensive!!!

How many Deferreds are created here ?

timeout( 2000 ).then( function() {

	return timeout( 3000 );

} ).done( function() {

	// 5 seconds later!

} );

in the wild

retry on 409 - Conflict

function getUserData( id ) {
	return ajax( "path/to/service", {
		data: {
			id: id
		}
	} ).then( null, function( jqXHR ) {

		return jqXHR.status === 409 ? getUserData( id ) : jqXHR;

	} );
}

cache it

var cache = {};

function getCachedUserData( id ) {
	if ( !cache[ id ] ) {
		cache[ id ] = getUserData( id );
	}
	return cache[ id ];
}

each id will only be requested once!

getCachedUserData( 750412 ); // put in cache
getCachedUserData( 750412 ); // taken from cache even if not complete

get multiple data at once

function getMultipleData() {
	return $.when.apply( null, $.map( arguments, getCachedUserData ) );
}

getMultipleData( 860224, 750412, 500428, 860224 );

make it optimal

function _getMultipleData( ids ) {
	return ajax( "path/to/service", {
		data: {
			ids: ids
		}
	} ).then( null, function( jqXHR ) {

		return jqXHR.status === 409 ? _getMultipleData( ids ) : jqXHR;

	} );
}
var nextRequest;
var idsForNextRequest;

function getUserData( id ) {
	if ( !nextRequest ) {

		idsForNextRequest = [];

		nextRequest = timeout( 400 ).then( function() {
			nextRequest = undefined;
			return _getMultipleData( idsForNextRequest );
		} );

	}

	idsForNextRequest.push( id );

	return nextRequest.then( function( data ) {
		return data[ id ];
	} );
}

now, how many requests here?

getMultipleData( [ 860224, 750412, 500428, 860224 ] );

setTimeout( function() {
	getMultipleData( [ 750412, 500428, 860224, 660712 ] );
}, 2000 );

Conclusion?

talk.state() === "questions";
@seutje

This comment has been minimized.

Copy link

commented Sep 12, 2013

"avanced" a reference to the non-profit, or just a typo?

@jaubourg

This comment has been minimized.

Copy link
Owner Author

commented Sep 12, 2013

Fixed, thanks @seutje :)

@gfranko

This comment has been minimized.

Copy link

commented Feb 28, 2014

This is fantastic. Thanks for making this and presenting!

@staabm

This comment has been minimized.

Copy link

commented Mar 10, 2014

$.Deferred() can take an init function (use it! use it! use it!)

great tip! saves you from creating crazy named variables ;-). thanks a lot!

@staabm

This comment has been minimized.

Copy link

commented Mar 11, 2014

@jaubourg

how to promote rejection from an nested promise into outer promise?
Is it always required to manually do things like

inner.fail(function() {
  outer.reject();
});

or is there a smarter way to make this "dependency" clear..? ATM it feels like one could easily "break" a promise chain because of forgotten state-promotion of an inner promise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.