README.md 7.26 KB
Newer Older
Stefan Penner's avatar
Stefan Penner committed
1
# RSVP.js  [![Build Status](https://secure.travis-ci.org/tildeio/rsvp.js.png?branch=master)](http://travis-ci.org/tildeio/rsvp.js)
Yehuda Katz's avatar
Yehuda Katz committed
2

Tom Dale's avatar
Tom Dale committed
3
RSVP.js provides simple tools for organizing asynchronous code.
Yehuda Katz's avatar
Yehuda Katz committed
4

5
Specifically, it is a tiny implementation of Promises/A+ and a
Yehuda Katz's avatar
Yehuda Katz committed
6 7
mixin for turning objects into event targets.

Cyril Fluck's avatar
Cyril Fluck committed
8
It works in node and the browser.
Yehuda Katz's avatar
Yehuda Katz committed
9

Stefan Penner's avatar
Stefan Penner committed
10 11 12
## downloads

- [rsvp-latest](http://rsvpjs-builds.s3.amazonaws.com/rsvp-latest.js)
13
- [rsvp-latest (amd)](http://rsvpjs-builds.s3.amazonaws.com/rsvp-latest.amd.js)
Stefan Penner's avatar
Stefan Penner committed
14

Yehuda Katz's avatar
Yehuda Katz committed
15 16
## Promises

17
`RSVP.Promise` is an implementation of
18 19
[Promises/A+](http://promises-aplus.github.com/promises-spec/) that passes the
[test suite](https://github.com/promises-aplus/promises-tests).
Yehuda Katz's avatar
Yehuda Katz committed
20 21 22 23 24 25 26 27 28 29 30 31 32 33

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](http://taskjs.org/), 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
34 35 36 37 38 39
var promise = new RSVP.Promise(function(resolve, reject){
  // succeed
  resolve(value);
  // or reject
  reject(error);
});
Yehuda Katz's avatar
Yehuda Katz committed
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

promise.then(function(value) {
  // success
}, function(value) {
  // 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) {
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
  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); }
      }
    };
  });
Yehuda Katz's avatar
Yehuda Katz committed
70 71 72 73 74 75 76 77 78 79 80 81 82

  return promise;
};

getJSON("/posts.json").then(function(json) {
  // continue
}, function(error) {
  // handle errors
});
```

### Chaining

83
One of the really awesome features of Promises/A+ promises are that they
Yehuda Katz's avatar
Yehuda Katz committed
84 85 86 87 88 89 90 91 92 93 94
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
Stefan Penner's avatar
Stefan Penner committed
95
});
Yehuda Katz's avatar
Yehuda Katz committed
96 97 98 99 100 101 102 103 104 105 106
```

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 posts and comments
Stefan Penner's avatar
Stefan Penner committed
107
});
Yehuda Katz's avatar
Yehuda Katz committed
108 109 110 111 112 113 114 115 116 117 118
```

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) {

119
}).then(null, function(error) {
120 121
  // since no rejection handler was passed to the
  // first `.then`, the error propagates.
Yehuda Katz's avatar
Yehuda Katz committed
122 123 124 125 126 127 128 129 130 131 132 133
});
```

You can use this to emulate `try/catch` logic in synchronous code.
Simply chain as many resolve callbacks as a 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
134
}).then(null, function(error) {
Yehuda Katz's avatar
Yehuda Katz committed
135 136 137 138
  // handle errors in either of the two requests
});
```

139 140 141 142 143 144 145 146 147 148 149
You can also use `fail` for error handling, which is a shortcut for
`then(null, rejection)`, like so:

```javascript
getJSON("/post/1.json").then(function(post) {
  return getJSON(post.commentURL);
}).fail(function(error) {
  // handle errors
});
```

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
## 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 postIds = [2, 3, 5, 7, 11, 13];
var promises = [];

for(var i = 0; i < postIds.length; i++) {
	promises.push(getJSON("/post/" + postIds[i] + ".json"));
}

RSVP.all(promises).then(function(posts) {
	// posts contains an array of results for the given promises
});
```

Yehuda Katz's avatar
Yehuda Katz committed
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
## TaskJS

The [TaskJS](http://taskjs.org/) 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
}).then(null, function(error) {
  // 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!

## Event Target

RSVP also provides a mixin that you can use to convert any object into
an event target. The promises implementation uses `RSVP.EventTarget`, so
`RSVP` exposes it for your own use.

### Basic Usage

The basic usage of `RSVP.EventTarget` is to mix it into an object, then
use `on` and `trigger` to register listeners and trigger them.

```javascript
var object = {};

RSVP.EventTarget.mixin(object);

object.on("finished", function(event) {
  // handle event
});

object.trigger("finished", { detail: value });
```

### Prototypes

You can mix `RSVP.EventTarget` into a prototype and it will work as
expected.

```javascript
var Person = function() {};
RSVP.EventTarget.mixin(Person.prototype);

var yehuda = new Person();
var tom = new Person();

yehuda.on("poke", function(event) {
  console.log("Yehuda says OW");
});

tom.on("poke", function(event) {
  console.log("Tom says OW");
});

yehuda.trigger("poke");
tom.trigger("poke");
```

The example will work as expected. If you mix `RSVP.EventTarget` into a
constructor's prototype, each instance of that constructor will get its
own callbacks.
263 264 265 266 267 268 269 270 271 272

## Building & Testing

This package uses the [grunt-microlib](https://github.com/thomasboyt/grunt-microlib) package for building.

Custom tasks:

* `grunt test` - Run Mocha tests through Node and PhantomJS.
* `grunt test:phantom` - Run Mocha tests through PhantomJS (browser build).
* `grunt test:node` - Run Mocha tests through Node (CommonJS build).