README.md 11.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
# RSVP.js  [![Build Status](https://secure.travis-ci.org/tildeio/rsvp.js.svg?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)
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`.