RSVP.js
RSVP.js provides simple tools for organizing asynchronous code.
Specifically, it is a tiny implementation of Promises/A+.
It works in node and the browser (IE6+, all the popular evergreen ones).
downloads
Promises
Although RSVP is es6 compliant, it does bring along some extra toys. If you would prefer a strict es6 subset, I would suggest checking out our sibling project https://github.com/jakearchibald/es6-promise, It is RSVP but stripped down to the es6 spec features.
Bower
bower install -S rsvp
NPM
npm install --save rsvp
RSVP.Promise
is an implementation of
Promises/A+ that passes the
test suite.
It delivers all promises asynchronously, even if the value is already
available, to help you write consistent code that doesn't change if the
underlying data provider changes from synchronous to asynchronous.
It is compatible with TaskJS, a library by Dave
Herman of Mozilla that uses ES6 generators to allow you to write
synchronous code with promises. It currently works in Firefox, and will
work in any browser that adds support for ES6 generators. See the
section below on TaskJS for more information.
Basic Usage
var RSVP = require('rsvp');
var promise = new RSVP.Promise(function(resolve, reject) {
resolve(value);
reject(error);
});
promise.then(function(value) {
}, function(value) {
});
Once a promise has been resolved or rejected, it cannot be resolved or
rejected again.
Here is an example of a simple XHR2 wrapper written using RSVP.js:
var getJSON = function(url) {
var promise = new RSVP.Promise(function(resolve, reject){
var client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
function handler() {
if (this.readyState === this.DONE) {
if (this.status === 200) { resolve(this.response); }
else { reject(this); }
}
};
});
return promise;
};
getJSON("/posts.json").then(function(json) {
}, function(error) {
});
Chaining
One of the really awesome features of Promises/A+ promises are that they
can be chained together. In other words, the return value of the first
resolve handler will be passed to the second resolve handler.
If you return a regular value, it will be passed, as is, to the next
handler.
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
});
The really awesome part comes when you return a promise from the first
handler:
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
});
This allows you to flatten out nested callbacks, and is the main feature
of promises that prevents "rightward drift" in programs with a lot of
asynchronous code.
Errors also propagate:
getJSON("/posts.json").then(function(posts) {
}).catch(function(error) {
});
You can use this to emulate try/catch
logic in synchronous code.
Simply chain as many resolve callbacks as a you want, and add a failure
handler at the end to catch errors.
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
}).catch(function(error) {
});
You can also use catch
for error handling, which is a shortcut for
then(null, rejection)
, like so:
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).catch(function(error) {
});
Error Handling
There are times when dealing with promises that it seems like any errors
are being 'swallowed', and not properly raised. This makes it extremely
difficult to track down where a given issue is coming from. Thankfully,
RSVP
has a solution for this problem built in.
You can register functions to be called when an uncaught error occurs
within your promises. These callback functions can be anything, but a common
practice is to call console.assert
to dump the error to the console.
RSVP.on('error', function(reason) {
console.assert(false, reason);
});
RSVP
allows Promises to be labeled: Promise.resolve(value, 'I AM A LABEL')
If provided, this label is passed as the second argument to RSVP.on('error')
RSVP.on('error', function(reason, label) {
if (label) {
console.error(label);
}
console.assert(false, reason);
});
NOTE: promises do allow for errors to be handled asynchronously, so
this callback may result in false positives.
NOTE: Usage of RSVP.configure('onerror', yourCustomFunction);
is
deprecated in favor of using RSVP.on
.
Finally
finally
will be invoked regardless of the promise's fate, just as native
try/catch/finally behaves.
findAuthor().catch(function(reason){
return findOtherAuthor();
}).finally(function(){
});
Arrays of promises
Sometimes you might want to work with many promises at once. If you
pass an array of promises to the all()
method it will return a new
promise that will be fulfilled when all of the promises in the array
have been fulfilled; or rejected immediately if any promise in the array
is rejected.
var promises = [2, 3, 5, 7, 11, 13].map(function(id){
return getJSON("/post/" + id + ".json");
});
RSVP.all(promises).then(function(posts) {
}).catch(function(reason){
});
Hash of promises
If you need to reference many promises at once (like all()
), but would like
to avoid encoding the actual promise order you can use hash()
. If you pass
an object literal (where the values are promises) to the hash()
method it will
return a new promise that will be fulfilled when all of the promises have been
fulfilled; or rejected immediately if any promise is rejected.
The key difference to the all()
function is that both the fulfillment value
and the argument to the hash()
function are object literals. This allows
you to simply reference the results directly off the returned object without
having to remember the initial order like you would with all()
.
var promises = {
posts: getJSON("/posts.json"),
users: getJSON("/users.json")
};
RSVP.hash(promises).then(function(results) {
console.log(results.users)
console.log(results.posts)
});
All settled and hash settled
Sometimes you want to work with several promises at once, but instead of
rejecting immediately if any promise is rejected, as with all()
or hash()
,
you want to be able to inspect the results of all your promises, whether
they fulfill or reject. For this purpose, you can use allSettled()
and
hashSettled()
. These work exactly like all()
and hash()
, except that
they fulfill with an array or hash (respectively) of the constituent promises'
result states. Each state object will either indicate fulfillment or
rejection, and provide the corresponding value or reason. The states will take
one of the following formats:
{ state: 'fulfilled', value: value }
or
{ state: 'rejected', reason: reason }
Deferred
The RSVP.Promise
constructor is generally a better, less error-prone choice
than RSVP.defer()
. Promises are recommended unless the specific
properties of deferred are needed.
Sometimes one needs to create a deferred object, without immediately specifying
how it will be resolved. These deferred objects are essentially a wrapper around
a promise, whilst providing late access to the resolve()
and reject()
methods.
A deferred object has this form: { promise, resolve(x), reject(r) }
.
var deferred = RSVP.defer();
deferred.promise
deferred.resolve();
TaskJS
The TaskJS library makes it possible to take
promises-oriented code and make it synchronous using ES6 generators.
Let's review an earlier example:
getJSON("/post/1.json").then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
}).catch(function(reason) {
});
Without any changes to the implementation of getJSON
, you could write
the following code with TaskJS:
spawn(function *() {
try {
var post = yield getJSON("/post/1.json");
var comments = yield getJSON(post.commentURL);
} catch(error) {
}
});
In the above example, function *
is new syntax in ES6 for
generators.
Inside a generator, yield
pauses the generator, returning control to
the function that invoked the generator. In this case, the invoker is a
special function that understands the semantics of Promises/A, and will
automatically resume the generator as soon as the promise is resolved.
The cool thing here is the same promises that work with current
JavaScript using .then
will work seamlessly with TaskJS once a browser
has implemented it!
Instrumentation
function listener (event) {
event.guid
event.childGuid
event.eventName
event.detail
event.label
event.timeStamp
event.stack
}
RSVP.configure('instrument', true | false);
RSVP.configure('instrument-with-stack', true | false);
RSVP.on('created', listener);
RSVP.on('chained', listener);
RSVP.on('fulfilled', listener);
RSVP.on('rejected', listener);
Events are only triggered when RSVP.configure('instrument')
is true, although
listeners can be registered at any time.
Building & Testing
Custom tasks:
npm test
- build & testnpm test:node
- build & test just nodenpm test:server
- build/watch & testnpm run build
- Buildnpm run build:production
- Build production (with minified output)npm start
- build, watch and run interactive server at http://localhost:4200'
Releasing
Check what release-it will do by running npm run-script dry-run-release
.
To actually release, run node_modules/.bin/release-it
.