UNPKG

11.5 kBMarkdownView Raw
1# RSVP.js [![Build Status](https://secure.travis-ci.org/tildeio/rsvp.js.png?branch=master)](http://travis-ci.org/tildeio/rsvp.js) [![Inline docs](http://inch-ci.org/github/tildeio/rsvp.js.svg?branch=master)](http://inch-ci.org/github/tildeio/rsvp.js)
2
3RSVP.js provides simple tools for organizing asynchronous code.
4
5Specifically, it is a tiny implementation of Promises/A+.
6
7It works in node and the browser (IE6+, all the popular evergreen ones).
8
9## downloads
10
11- [rsvp-latest](http://rsvpjs-builds.s3.amazonaws.com/rsvp-latest.js)
12- [rsvp-latest (minified)](http://rsvpjs-builds.s3.amazonaws.com/rsvp-latest.min.js)
13
14## Promises
15
16Although 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/stefanpenner/es6-promise, It is RSVP but stripped down to the es6 spec features.
17
18## Bower
19
20`bower install -S rsvp`
21
22## NPM
23
24`npm install --save rsvp`
25
26`RSVP.Promise` is an implementation of
27[Promises/A+](http://promises-aplus.github.com/promises-spec/) that passes the
28[test suite](https://github.com/promises-aplus/promises-tests).
29
30It delivers all promises asynchronously, even if the value is already
31available, to help you write consistent code that doesn't change if the
32underlying data provider changes from synchronous to asynchronous.
33
34It is compatible with [TaskJS](http://taskjs.org/), a library by Dave
35Herman of Mozilla that uses ES6 generators to allow you to write
36synchronous code with promises. It currently works in Firefox, and will
37work in any browser that adds support for ES6 generators. See the
38section below on TaskJS for more information.
39
40### Basic Usage
41
42```javascript
43var RSVP = require('rsvp');
44
45var promise = new RSVP.Promise(function(resolve, reject) {
46 // succeed
47 resolve(value);
48 // or reject
49 reject(error);
50});
51
52promise.then(function(value) {
53 // success
54}).catch(function(error) {
55 // failure
56});
57```
58
59Once a promise has been resolved or rejected, it cannot be resolved or
60rejected again.
61
62Here is an example of a simple XHR2 wrapper written using RSVP.js:
63
64```javascript
65var getJSON = function(url) {
66 var promise = new RSVP.Promise(function(resolve, reject){
67 var client = new XMLHttpRequest();
68 client.open("GET", url);
69 client.onreadystatechange = handler;
70 client.responseType = "json";
71 client.setRequestHeader("Accept", "application/json");
72 client.send();
73
74 function handler() {
75 if (this.readyState === this.DONE) {
76 if (this.status === 200) { resolve(this.response); }
77 else { reject(this); }
78 }
79 };
80 });
81
82 return promise;
83};
84
85getJSON("/posts.json").then(function(json) {
86 // continue
87}).catch(function(error) {
88 // handle errors
89});
90```
91
92### Chaining
93
94One of the really awesome features of Promises/A+ promises are that they
95can be chained together. In other words, the return value of the first
96resolve handler will be passed to the second resolve handler.
97
98If you return a regular value, it will be passed, as is, to the next
99handler.
100
101```javascript
102getJSON("/posts.json").then(function(json) {
103 return json.post;
104}).then(function(post) {
105 // proceed
106});
107```
108
109The really awesome part comes when you return a promise from the first
110handler:
111
112```javascript
113getJSON("/post/1.json").then(function(post) {
114 // save off post
115 return getJSON(post.commentURL);
116}).then(function(comments) {
117 // proceed with access to post and comments
118});
119```
120
121This allows you to flatten out nested callbacks, and is the main feature
122of promises that prevents "rightward drift" in programs with a lot of
123asynchronous code.
124
125Errors also propagate:
126
127```javascript
128getJSON("/posts.json").then(function(posts) {
129
130}).catch(function(error) {
131 // since no rejection handler was passed to the
132 // first `.then`, the error propagates.
133});
134```
135
136You can use this to emulate `try/catch` logic in synchronous code.
137Simply chain as many resolve callbacks as a you want, and add a failure
138handler at the end to catch errors.
139
140```javascript
141getJSON("/post/1.json").then(function(post) {
142 return getJSON(post.commentURL);
143}).then(function(comments) {
144 // proceed with access to posts and comments
145}).catch(function(error) {
146 // handle errors in either of the two requests
147});
148```
149
150## Error Handling
151
152There are times when dealing with promises that it seems like any errors
153are being 'swallowed', and not properly raised. This makes it extremely
154difficult to track down where a given issue is coming from. Thankfully,
155`RSVP` has a solution for this problem built in.
156
157You can register functions to be called when an uncaught error occurs
158within your promises. These callback functions can be anything, but a common
159practice is to call `console.assert` to dump the error to the console.
160
161```javascript
162RSVP.on('error', function(reason) {
163 console.assert(false, reason);
164});
165```
166
167`RSVP` allows Promises to be labeled: `Promise.resolve(value, 'I AM A LABEL')`
168If provided, this label is passed as the second argument to `RSVP.on('error')`
169
170```javascript
171RSVP.on('error', function(reason, label) {
172 if (label) {
173 console.error(label);
174 }
175
176 console.assert(false, reason);
177});
178```
179
180
181**NOTE:** promises do allow for errors to be handled asynchronously, so
182this callback may result in false positives.
183
184**NOTE:** Usage of `RSVP.configure('onerror', yourCustomFunction);` is
185deprecated in favor of using `RSVP.on`.
186
187
188## Finally
189
190`finally` will be invoked regardless of the promise's fate, just as native
191try/catch/finally behaves.
192
193```js
194findAuthor().catch(function(reason){
195 return findOtherAuthor();
196}).finally(function(){
197 // author was either found, or not
198});
199```
200
201
202## Arrays of promises
203
204Sometimes you might want to work with many promises at once. If you
205pass an array of promises to the `all()` method it will return a new
206promise that will be fulfilled when all of the promises in the array
207have been fulfilled; or rejected immediately if any promise in the array
208is rejected.
209
210```javascript
211var promises = [2, 3, 5, 7, 11, 13].map(function(id){
212 return getJSON("/post/" + id + ".json");
213});
214
215RSVP.all(promises).then(function(posts) {
216 // posts contains an array of results for the given promises
217}).catch(function(reason){
218 // if any of the promises fails.
219});
220```
221
222## Hash of promises
223
224If you need to reference many promises at once (like `all()`), but would like
225to avoid encoding the actual promise order you can use `hash()`. If you pass
226an object literal (where the values are promises) to the `hash()` method it will
227return a new promise that will be fulfilled when all of the promises have been
228fulfilled; or rejected immediately if any promise is rejected.
229
230The key difference to the `all()` function is that both the fulfillment value
231and the argument to the `hash()` function are object literals. This allows
232you to simply reference the results directly off the returned object without
233having to remember the initial order like you would with `all()`.
234
235```javascript
236var promises = {
237 posts: getJSON("/posts.json"),
238 users: getJSON("/users.json")
239};
240
241RSVP.hash(promises).then(function(results) {
242 console.log(results.users) // print the users.json results
243 console.log(results.posts) // print the posts.json results
244});
245```
246
247## All settled and hash settled
248
249Sometimes you want to work with several promises at once, but instead of
250rejecting immediately if any promise is rejected, as with `all()` or `hash()`,
251you want to be able to inspect the results of all your promises, whether
252they fulfill or reject. For this purpose, you can use `allSettled()` and
253`hashSettled()`. These work exactly like `all()` and `hash()`, except that
254they fulfill with an array or hash (respectively) of the constituent promises'
255result states. Each state object will either indicate fulfillment or
256rejection, and provide the corresponding value or reason. The states will take
257one of the following formats:
258
259```javascript
260{ state: 'fulfilled', value: value }
261 or
262{ state: 'rejected', reason: reason }
263```
264
265## Deferred
266
267> The `RSVP.Promise` constructor is generally a better, less error-prone choice
268> than `RSVP.defer()`. Promises are recommended unless the specific
269> properties of deferred are needed.
270
271Sometimes one needs to create a deferred object, without immediately specifying
272how it will be resolved. These deferred objects are essentially a wrapper around
273a promise, whilst providing late access to the `resolve()` and `reject()` methods.
274
275A deferred object has this form: `{ promise, resolve(x), reject(r) }`.
276
277```javascript
278var deferred = RSVP.defer();
279// ...
280deferred.promise // access the promise
281// ...
282deferred.resolve();
283
284```
285
286## TaskJS
287
288The [TaskJS](http://taskjs.org/) library makes it possible to take
289promises-oriented code and make it synchronous using ES6 generators.
290
291Let's review an earlier example:
292
293```javascript
294getJSON("/post/1.json").then(function(post) {
295 return getJSON(post.commentURL);
296}).then(function(comments) {
297 // proceed with access to posts and comments
298}).catch(function(reason) {
299 // handle errors in either of the two requests
300});
301```
302
303Without any changes to the implementation of `getJSON`, you could write
304the following code with TaskJS:
305
306```javascript
307spawn(function *() {
308 try {
309 var post = yield getJSON("/post/1.json");
310 var comments = yield getJSON(post.commentURL);
311 } catch(error) {
312 // handle errors
313 }
314});
315```
316
317In the above example, `function *` is new syntax in ES6 for
318[generators](http://wiki.ecmascript.org/doku.php?id=harmony:generators).
319Inside a generator, `yield` pauses the generator, returning control to
320the function that invoked the generator. In this case, the invoker is a
321special function that understands the semantics of Promises/A, and will
322automatically resume the generator as soon as the promise is resolved.
323
324The cool thing here is the same promises that work with current
325JavaScript using `.then` will work seamlessly with TaskJS once a browser
326has implemented it!
327
328## Instrumentation
329
330```js
331function listener (event) {
332 event.guid // guid of promise. Must be globally unique, not just within the implementation
333 event.childGuid // child of child promise (for chained via `then`)
334 event.eventName // one of ['created', 'chained', 'fulfilled', 'rejected']
335 event.detail // fulfillment value or rejection reason, if applicable
336 event.label // label passed to promise's constructor
337 event.timeStamp // milliseconds elapsed since 1 January 1970 00:00:00 UTC up until now
338 event.stack // stack at the time of the event. (if 'instrument-with-stack' is true)
339}
340
341RSVP.configure('instrument', true | false);
342// capturing the stacks is slow, so you also have to opt in
343RSVP.configure('instrument-with-stack', true | false);
344
345// events
346RSVP.on('created', listener);
347RSVP.on('chained', listener);
348RSVP.on('fulfilled', listener);
349RSVP.on('rejected', listener);
350```
351
352Events are only triggered when `RSVP.configure('instrument')` is true, although
353listeners can be registered at any time.
354
355## Building & Testing
356
357Custom tasks:
358
359* `npm test` - build & test
360* `npm test:node` - build & test just node
361* `npm test:server` - build/watch & test
362* `npm run build` - Build
363* `npm run build:production` - Build production (with minified output)
364* `npm start` - build, watch and run interactive server at http://localhost:4200'
365
366## Releasing
367
368Check what release-it will do by running `npm run-script dry-run-release`.
369To actually release, run `node_modules/.bin/release-it`.