- Creative-Area: http://www.creative-area.net
- jQuery Core:
$.ajax,$.Deferred, ... - W3C: XMLHttpRequest
- github: jaubourg
- www: http://jaubourg.net
- email: [email protected]
- twitter: @jaubourg
- Learn about Promises
- Rethink what you think you know about them
- Learn about
$.when$.Deferredthen()
Oh and I gist-wrapped it for you (take that hipsters!)
THIS TALK IS NOT SEQUENTIAL
So sit back, relax, think asynchronously
ENJOY THE RIDE
- 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()-- akapipe()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!
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();
}- 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
} );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; // trueWhy 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
} );- 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(); // throwsIt's our fault really, a Deferred:
- should be Observable
- shouldn't be a Promise
Time to break the web again!
- some objects are Observable through a Promise
- a Promise is an Interface
- a Promise is a tri-state boolean
then(),done()andfail()are conditionals$.when()is a logical AND$.Deferred()can be used to:- create a Promise
- control its state
How about some...
promise.done( fn1 ).fail( fn2 ).always( fn3 ) === promise; // truevar 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 );var defer = Deferred();
defer.done( function( number ) {
console.log( "called with " + number );
} );
defer.resolve( 1 );
defer.resolve( 2 );$.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
} );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 ]
} );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 );
}
} );How many Deferreds are created here ?
timeout( 2000 ).then( function() {
return timeout( 3000 );
} ).done( function() {
// 5 seconds later!
} );function getUserData( id ) {
return ajax( "path/to/service", {
data: {
id: id
}
} ).then( null, function( jqXHR ) {
return jqXHR.status === 409 ? getUserData( id ) : jqXHR;
} );
}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 completefunction getMultipleData() {
return $.when.apply( null, $.map( arguments, getCachedUserData ) );
}
getMultipleData( 860224, 750412, 500428, 860224 );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 );talk.state() === "questions";
"avanced" a reference to the non-profit, or just a typo?