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`. |