Presented by Cory Forsyth / @bantic
“ A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its `then` method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled. ”
var successHandler = function(successValue){
console.log("I am called when the promise successfully resolves.");
};
var rejectionHandler = function(reason){
console.log("I am called when the promise is rejected for 'reason'.");
};
// doSomethingAsync() returns a Promise
doSomethingAsync().then( successHandler, rejectionHandler );
A pattern in javascript for coordinating asynchronous activity.
Examples:
connectToDb(
function(connection) { // got connection
connection.doSomething();
}, function(err) { // got error
console.error("Could not connect to db: " + err);
}
);
var transferComplete = function(){
var json = JSON.parse(this.responseText)
console.log('transferComplete, user name is: ' + json.name);
};
var transferFailed = function(){
console.error('transferFailed');
};
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.github.com/users/bantic', true);
xhr.addEventListener("load", transferComplete, false);
xhr.addEventListener("error", transferFailed, false);
xhr.send();
Callback version
connectToDb(
function(connection) { // got connection
connection.doSomething();
}, function(err) { // got error
console.error("Could not connect to db: " + err);
}
);
Promise version
connectToDb().then(
function(connection){ // got connection
connection.doSomething();
}, function(err) { // got error
console.error("Could not connect to db: " + err);
}
);
connectToDb(function(connection) { // got connection
connection.doSomething(function(result) {
result.name = 'new name';
result.save(function(){
console.log('saved successfully');
}, function(err){
console.error('error saving');
});
}, function(err) {
console.error("Error doing something");
})
}, function(err) { // got error
console.error("Could not connect to db: " + err);
}
);
Using promises
connectToDb().then(function(connection){ // connectToDb returns a promise
return connection.doSomething(); // connection.doSomething returns a promise
}).then(function(result){
result.name = 'new name';
return result.save(); // result.save returns a promise
}).then(function(){
console.log('saved successfully.');
}, function(err){
console.error("Error.");
}
);
var App = function(){
this.init = function(){
this.loadData();
};
this.start = function(data){
console.log("Started for user: " + data.name);
};
// many more lines ...
// How does the app actually start?
};
var App = function(){
this.init = function(){
this.loadData();
};
this.start = function(data){
console.log("Started for user: " + data.name);
};
// ... many lines later
// ... oh, loadData calls start
this.loadData = function(){
var that = this;
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.github.com/users/bantic', true);
xhr.addEventListener('load', function(){
var data = JSON.parse(this.responseText);
that.start(data);
}, false);
xhr.send();
};
};
var App = function(){
this.init = function(){
var that = this;
this.loadData().then( function(data) {
that.start(data);
};
};
this.start = function(data){
console.log("Started for user: " + data.name);
};
// ... many lines
this.loadData = function(){
return new RSVP.Promise( function(resolve, reject){
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.github.com/users/bantic', true);
xhr.addEventListener('load', function(){
var data = JSON.parse(this.responseText);
resolve(data);
}, false);
xhr.send();
});
};
};
promise2 = promise1.then(onFulfilled, onRejected);
2.2.7.2 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.
2.2.7.4 If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason.
When any promise in a chain of promises throws an exception, that promise will be rejected and the rest in the chain will be rejected for the same reason.
The rejection "falls through" until there is a rejection handler.
var rejectedPromiseHandler = function(reason){
console.error("Rejected with reason: " + reason);
};
doSomethingAsync() // gets called
.then( doSomethingAsync ) // gets called
.then( function(){ throw new Error("Oops!"); } )
.then( doSomethingAsync ) // never gets called,
// and since there is no rejection handler,
// the rejection 'falls through' to:
.then( null, rejectedPromiseHandler ); // rejectedPromiseHandler gets called
// with the thrown exception as its argument
var rejectedPromiseHandler = function(reason){
console.error("Rejected with reason: " + reason);
};
doSomethingAsync() // gets called
.then( doSomethingAsync ) // gets called
.then( function(){ return RSVP.reject('rejected'); } )
.then( doSomethingAsync ) // never gets called,
// and since there is no rejection handler,
// the rejection 'falls through' to:
.then( null, rejectedPromiseHandler ); // rejectedPromiseHandler gets called
// with argument 'rejected'
These are niceties provided by RSVP.js and not part of the Promises/A+ spec
An alias for .then(null, errorHandler)
// this:
doSomethingAsync()
.then( doSomethingElseAsync )
.then( null, rejectionHandler );
// Is the same as this:
doSomethingAsync()
.then( doSomethingElseAsync )
.fail( rejectionHandler );
Normally, a rejected promise will be handled by the first rejection handler. A thrown exception will be caught and handled by the next rejection handler.
doSomethingAsync()
.then( function(){ throw new Error('error');} )
.then( null, function(e){
console.error("The error is caught and handled here.");
throw e;
})
.then( null, function(e){
console.error("I will execute also, because e was thrown.");
});
But if you use RSVP.rethrow() then the error makes it out of the promise chain, where you can see it in your console.
doSomethingAsync()
.then( function(){ throw new Error('error');} )
.then( null, function(e){
console.error("The error is caught and handled here.");
RSVP.rethrow(e);
});
// the thrown error will
// exit the promise chain and
// the console will now display this error
This can be very helpful in debugging.
var async = function(callback){
setTimeout( function(){
callback();
}, 1000);
};
async( function(){
console.log("This happens after 1000ms");
});
var asyncWithPromise = function(){
return new RSVP.Promise( function(resolve, reject){
setTimeout( function(){
resolve();
}, 1000);
});
};
asyncWithPromise().then( function(){
console.log("This happens after 1000ms");
});
Gotcha: Make sure you return a promise if the code is async.
asyncWithPromise().then( function(){ // happens first
return asyncWithPromise(); // happens after the first finishes
}).then( function() {
return asyncWithPromise(); // happens after the second finishes
});
It is ok to return non-promise values from non-async code.
asyncWithPromise().then( function(){ // happens first
doSomethingSynchronous();
return someValue; // not returning a promise
}).then( function() {
return asyncWithPromise(); // returns a promise, which
// gets assimilated into the chain.
// The next 'then' doesn't fire until
// this promise is resolved.
}).then( function() { // this fires after the asyncWithPromise
// above resolves.
doSomethingElseSynchronous();
return someOtherValue;
});;
If return value is a promise, its 'success' value is passed to the next `then()`
If return value is a non-promise, that value is given to the next `then()`
asyncWithPromise().then( function(successValue){ // successValue is whatever
// asyncWithPromise `resolve()`'d with
return true;
}).then( function(successValue) { // successValue is true
return anotherAsyncWithPromise();
}).then( function(successValue) { // successValue is whatever
// anotherAsyncWithPromise
// `resolve()`'d with
return true;
});;
https://github.com/mixonic/rsvp-tools is a library we created with a set of wrappers to:
You can view this talk online at:
http://bit.ly/utahjs-promises