372 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			372 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| # RSVP.js  [](http://travis-ci.org/tildeio/rsvp.js) [](http://inch-ci.org/github/tildeio/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 (IE9+, all the popular evergreen ones).
 | |
| 
 | |
| ## downloads
 | |
| 
 | |
| - rsvp ([latest](https://cdn.jsdelivr.net/npm/rsvp/dist/rsvp.js) | [4.x](https://cdn.jsdelivr.net/npm/rsvp@4/dist/rsvp.js))
 | |
| - rsvp minified ([latest](https://cdn.jsdelivr.net/npm/rsvp/dist/rsvp.min.js) | [4.x](https://cdn.jsdelivr.net/npm/rsvp@4/dist/rsvp.min.js))
 | |
| 
 | |
| ## CDN
 | |
| 
 | |
| ```html
 | |
| <script src="https://cdn.jsdelivr.net/npm/rsvp@4/dist/rsvp.min.js"></script>
 | |
| ```
 | |
| 
 | |
| ## 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/stefanpenner/es6-promise, It is RSVP but stripped
 | |
| down to the ES6 spec features.
 | |
| 
 | |
| ## Node
 | |
| 
 | |
| ```sh
 | |
| yarn add --save rsvp
 | |
| # or ...
 | |
| npm install --save rsvp
 | |
| ```
 | |
| 
 | |
| `RSVP.Promise` is an implementation of
 | |
| [Promises/A+](http://promises-aplus.github.com/promises-spec/) that passes the
 | |
| [test suite](https://github.com/promises-aplus/promises-tests).
 | |
| 
 | |
| 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](https://github.com/mozilla/task.js), 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
 | |
| 
 | |
| ```javascript
 | |
| var RSVP = require('rsvp');
 | |
| 
 | |
| var promise = new RSVP.Promise(function(resolve, reject) {
 | |
|   // succeed
 | |
|   resolve(value);
 | |
|   // or reject
 | |
|   reject(error);
 | |
| });
 | |
| 
 | |
| promise.then(function(value) {
 | |
|   // success
 | |
| }).catch(function(error) {
 | |
|   // failure
 | |
| });
 | |
| ```
 | |
| 
 | |
| 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:
 | |
| 
 | |
| ```javascript
 | |
| 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) {
 | |
|   // continue
 | |
| }).catch(function(error) {
 | |
|   // handle errors
 | |
| });
 | |
| ```
 | |
| 
 | |
| ### 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.
 | |
| 
 | |
| ```javascript
 | |
| getJSON("/posts.json").then(function(json) {
 | |
|   return json.post;
 | |
| }).then(function(post) {
 | |
|   // proceed
 | |
| });
 | |
| ```
 | |
| 
 | |
| The really awesome part comes when you return a promise from the first handler:
 | |
| 
 | |
| ```javascript
 | |
| getJSON("/post/1.json").then(function(post) {
 | |
|   // save off post
 | |
|   return getJSON(post.commentURL);
 | |
| }).then(function(comments) {
 | |
|   // proceed with access to post and 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:
 | |
| 
 | |
| ```javascript
 | |
| getJSON("/posts.json").then(function(posts) {
 | |
| 
 | |
| }).catch(function(error) {
 | |
|   // since no rejection handler was passed to the
 | |
|   // first `.then`, the error propagates.
 | |
| });
 | |
| ```
 | |
| 
 | |
| You can use this to emulate `try/catch` logic in synchronous code. Simply chain
 | |
| as many resolve callbacks as you want, and add a failure handler at the end to
 | |
| catch errors.
 | |
| 
 | |
| ```javascript
 | |
| getJSON("/post/1.json").then(function(post) {
 | |
|   return getJSON(post.commentURL);
 | |
| }).then(function(comments) {
 | |
|   // proceed with access to posts and comments
 | |
| }).catch(function(error) {
 | |
|   // handle errors in either of the two requests
 | |
| });
 | |
| ```
 | |
| 
 | |
| ## 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.
 | |
| 
 | |
| ```javascript
 | |
| 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')`
 | |
| 
 | |
| ```javascript
 | |
| 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.
 | |
| 
 | |
| ## Finally
 | |
| 
 | |
| `finally` will be invoked regardless of the promise's fate, just as native
 | |
| try/catch/finally behaves.
 | |
| 
 | |
| ```js
 | |
| findAuthor().catch(function(reason){
 | |
|   return findOtherAuthor();
 | |
| }).finally(function(){
 | |
|   // author was either found, or not
 | |
| });
 | |
| ```
 | |
| 
 | |
| 
 | |
| ## 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.
 | |
| 
 | |
| ```javascript
 | |
| var promises = [2, 3, 5, 7, 11, 13].map(function(id){
 | |
|   return getJSON("/post/" + id + ".json");
 | |
| });
 | |
| 
 | |
| RSVP.all(promises).then(function(posts) {
 | |
|   // posts contains an array of results for the given promises
 | |
| }).catch(function(reason){
 | |
|   // if any of the promises fails.
 | |
| });
 | |
| ```
 | |
| 
 | |
| ## 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()`.
 | |
| 
 | |
| ```javascript
 | |
| var promises = {
 | |
|   posts: getJSON("/posts.json"),
 | |
|   users: getJSON("/users.json")
 | |
| };
 | |
| 
 | |
| RSVP.hash(promises).then(function(results) {
 | |
|   console.log(results.users) // print the users.json results
 | |
|   console.log(results.posts) // print the posts.json results
 | |
| });
 | |
| ```
 | |
| 
 | |
| ## 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:
 | |
| 
 | |
| ```javascript
 | |
| { 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) }`.
 | |
| 
 | |
| ```javascript
 | |
| var deferred = RSVP.defer();
 | |
| // ...
 | |
| deferred.promise // access the promise
 | |
| // ...
 | |
| deferred.resolve();
 | |
| 
 | |
| ```
 | |
| 
 | |
| ## TaskJS
 | |
| 
 | |
| The [TaskJS](https://github.com/mozilla/task.js) library makes it possible to take
 | |
| promises-oriented code and make it synchronous using ES6 generators.
 | |
| 
 | |
| Let's review an earlier example:
 | |
| 
 | |
| ```javascript
 | |
| getJSON("/post/1.json").then(function(post) {
 | |
|   return getJSON(post.commentURL);
 | |
| }).then(function(comments) {
 | |
|   // proceed with access to posts and comments
 | |
| }).catch(function(reason) {
 | |
|   // handle errors in either of the two requests
 | |
| });
 | |
| ```
 | |
| 
 | |
| Without any changes to the implementation of `getJSON`, you could write
 | |
| the following code with TaskJS:
 | |
| 
 | |
| ```javascript
 | |
| spawn(function *() {
 | |
|   try {
 | |
|     var post = yield getJSON("/post/1.json");
 | |
|     var comments = yield getJSON(post.commentURL);
 | |
|   } catch(error) {
 | |
|     // handle errors
 | |
|   }
 | |
| });
 | |
| ```
 | |
| 
 | |
| In the above example, `function *` is new syntax in ES6 for
 | |
| [generators](http://wiki.ecmascript.org/doku.php?id=harmony: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
 | |
| 
 | |
| ```js
 | |
| function listener (event) {
 | |
|   event.guid      // guid of promise. Must be globally unique, not just within the implementation
 | |
|   event.childGuid // child of child promise (for chained via `then`)
 | |
|   event.eventName // one of ['created', 'chained', 'fulfilled', 'rejected']
 | |
|   event.detail    // fulfillment value or rejection reason, if applicable
 | |
|   event.label     // label passed to promise's constructor
 | |
|   event.timeStamp // milliseconds elapsed since 1 January 1970 00:00:00 UTC up until now
 | |
|   event.stack     // stack at the time of the event. (if  'instrument-with-stack' is true)
 | |
| }
 | |
| 
 | |
| RSVP.configure('instrument', true | false);
 | |
| // capturing the stacks is slow, so you also have to opt in
 | |
| RSVP.configure('instrument-with-stack', true | false);
 | |
| 
 | |
| // events
 | |
| 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 & test
 | |
| * `npm test:node` - build & test just node
 | |
| * `npm test:server` - build/watch & test
 | |
| * `npm run build` - Build
 | |
| * `npm 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`.
 |