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 |
|
3 | RSVP.js provides simple tools for organizing asynchronous code.
|
4 |
|
5 | Specifically, it is a tiny implementation of Promises/A+.
|
6 |
|
7 | It 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 |
|
16 | 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/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 |
|
30 | It delivers all promises asynchronously, even if the value is already
|
31 | available, to help you write consistent code that doesn't change if the
|
32 | underlying data provider changes from synchronous to asynchronous.
|
33 |
|
34 | It is compatible with [TaskJS](http://taskjs.org/), a library by Dave
|
35 | Herman of Mozilla that uses ES6 generators to allow you to write
|
36 | synchronous code with promises. It currently works in Firefox, and will
|
37 | work in any browser that adds support for ES6 generators. See the
|
38 | section below on TaskJS for more information.
|
39 |
|
40 | ### Basic Usage
|
41 |
|
42 | ```javascript
|
43 | var RSVP = require('rsvp');
|
44 |
|
45 | var promise = new RSVP.Promise(function(resolve, reject) {
|
46 | // succeed
|
47 | resolve(value);
|
48 | // or reject
|
49 | reject(error);
|
50 | });
|
51 |
|
52 | promise.then(function(value) {
|
53 | // success
|
54 | }).catch(function(error) {
|
55 | // failure
|
56 | });
|
57 | ```
|
58 |
|
59 | Once a promise has been resolved or rejected, it cannot be resolved or
|
60 | rejected again.
|
61 |
|
62 | Here is an example of a simple XHR2 wrapper written using RSVP.js:
|
63 |
|
64 | ```javascript
|
65 | var 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 |
|
85 | getJSON("/posts.json").then(function(json) {
|
86 | // continue
|
87 | }).catch(function(error) {
|
88 | // handle errors
|
89 | });
|
90 | ```
|
91 |
|
92 | ### Chaining
|
93 |
|
94 | One of the really awesome features of Promises/A+ promises are that they
|
95 | can be chained together. In other words, the return value of the first
|
96 | resolve handler will be passed to the second resolve handler.
|
97 |
|
98 | If you return a regular value, it will be passed, as is, to the next
|
99 | handler.
|
100 |
|
101 | ```javascript
|
102 | getJSON("/posts.json").then(function(json) {
|
103 | return json.post;
|
104 | }).then(function(post) {
|
105 | // proceed
|
106 | });
|
107 | ```
|
108 |
|
109 | The really awesome part comes when you return a promise from the first
|
110 | handler:
|
111 |
|
112 | ```javascript
|
113 | getJSON("/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 |
|
121 | This allows you to flatten out nested callbacks, and is the main feature
|
122 | of promises that prevents "rightward drift" in programs with a lot of
|
123 | asynchronous code.
|
124 |
|
125 | Errors also propagate:
|
126 |
|
127 | ```javascript
|
128 | getJSON("/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 |
|
136 | You can use this to emulate `try/catch` logic in synchronous code.
|
137 | Simply chain as many resolve callbacks as a you want, and add a failure
|
138 | handler at the end to catch errors.
|
139 |
|
140 | ```javascript
|
141 | getJSON("/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 |
|
152 | There are times when dealing with promises that it seems like any errors
|
153 | are being 'swallowed', and not properly raised. This makes it extremely
|
154 | difficult to track down where a given issue is coming from. Thankfully,
|
155 | `RSVP` has a solution for this problem built in.
|
156 |
|
157 | You can register functions to be called when an uncaught error occurs
|
158 | within your promises. These callback functions can be anything, but a common
|
159 | practice is to call `console.assert` to dump the error to the console.
|
160 |
|
161 | ```javascript
|
162 | RSVP.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')`
|
168 | If provided, this label is passed as the second argument to `RSVP.on('error')`
|
169 |
|
170 | ```javascript
|
171 | RSVP.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
|
182 | this callback may result in false positives.
|
183 |
|
184 | **NOTE:** Usage of `RSVP.configure('onerror', yourCustomFunction);` is
|
185 | deprecated 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
|
191 | try/catch/finally behaves.
|
192 |
|
193 | ```js
|
194 | findAuthor().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 |
|
204 | Sometimes you might want to work with many promises at once. If you
|
205 | pass an array of promises to the `all()` method it will return a new
|
206 | promise that will be fulfilled when all of the promises in the array
|
207 | have been fulfilled; or rejected immediately if any promise in the array
|
208 | is rejected.
|
209 |
|
210 | ```javascript
|
211 | var promises = [2, 3, 5, 7, 11, 13].map(function(id){
|
212 | return getJSON("/post/" + id + ".json");
|
213 | });
|
214 |
|
215 | RSVP.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 |
|
224 | If you need to reference many promises at once (like `all()`), but would like
|
225 | to avoid encoding the actual promise order you can use `hash()`. If you pass
|
226 | an object literal (where the values are promises) to the `hash()` method it will
|
227 | return a new promise that will be fulfilled when all of the promises have been
|
228 | fulfilled; or rejected immediately if any promise is rejected.
|
229 |
|
230 | The key difference to the `all()` function is that both the fulfillment value
|
231 | and the argument to the `hash()` function are object literals. This allows
|
232 | you to simply reference the results directly off the returned object without
|
233 | having to remember the initial order like you would with `all()`.
|
234 |
|
235 | ```javascript
|
236 | var promises = {
|
237 | posts: getJSON("/posts.json"),
|
238 | users: getJSON("/users.json")
|
239 | };
|
240 |
|
241 | RSVP.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 |
|
249 | Sometimes you want to work with several promises at once, but instead of
|
250 | rejecting immediately if any promise is rejected, as with `all()` or `hash()`,
|
251 | you want to be able to inspect the results of all your promises, whether
|
252 | they fulfill or reject. For this purpose, you can use `allSettled()` and
|
253 | `hashSettled()`. These work exactly like `all()` and `hash()`, except that
|
254 | they fulfill with an array or hash (respectively) of the constituent promises'
|
255 | result states. Each state object will either indicate fulfillment or
|
256 | rejection, and provide the corresponding value or reason. The states will take
|
257 | one 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 |
|
271 | Sometimes one needs to create a deferred object, without immediately specifying
|
272 | how it will be resolved. These deferred objects are essentially a wrapper around
|
273 | a promise, whilst providing late access to the `resolve()` and `reject()` methods.
|
274 |
|
275 | A deferred object has this form: `{ promise, resolve(x), reject(r) }`.
|
276 |
|
277 | ```javascript
|
278 | var deferred = RSVP.defer();
|
279 | // ...
|
280 | deferred.promise // access the promise
|
281 | // ...
|
282 | deferred.resolve();
|
283 |
|
284 | ```
|
285 |
|
286 | ## TaskJS
|
287 |
|
288 | The [TaskJS](http://taskjs.org/) library makes it possible to take
|
289 | promises-oriented code and make it synchronous using ES6 generators.
|
290 |
|
291 | Let's review an earlier example:
|
292 |
|
293 | ```javascript
|
294 | getJSON("/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 |
|
303 | Without any changes to the implementation of `getJSON`, you could write
|
304 | the following code with TaskJS:
|
305 |
|
306 | ```javascript
|
307 | spawn(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 |
|
317 | In the above example, `function *` is new syntax in ES6 for
|
318 | [generators](http://wiki.ecmascript.org/doku.php?id=harmony:generators).
|
319 | Inside a generator, `yield` pauses the generator, returning control to
|
320 | the function that invoked the generator. In this case, the invoker is a
|
321 | special function that understands the semantics of Promises/A, and will
|
322 | automatically resume the generator as soon as the promise is resolved.
|
323 |
|
324 | The cool thing here is the same promises that work with current
|
325 | JavaScript using `.then` will work seamlessly with TaskJS once a browser
|
326 | has implemented it!
|
327 |
|
328 | ## Instrumentation
|
329 |
|
330 | ```js
|
331 | function 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 |
|
341 | RSVP.configure('instrument', true | false);
|
342 | // capturing the stacks is slow, so you also have to opt in
|
343 | RSVP.configure('instrument-with-stack', true | false);
|
344 |
|
345 | // events
|
346 | RSVP.on('created', listener);
|
347 | RSVP.on('chained', listener);
|
348 | RSVP.on('fulfilled', listener);
|
349 | RSVP.on('rejected', listener);
|
350 | ```
|
351 |
|
352 | Events are only triggered when `RSVP.configure('instrument')` is true, although
|
353 | listeners can be registered at any time.
|
354 |
|
355 | ## Building & Testing
|
356 |
|
357 | Custom 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 |
|
368 | Check what release-it will do by running `npm run-script dry-run-release`.
|
369 | To actually release, run `node_modules/.bin/release-it`.
|