• Stars
    star
    3,619
  • Rank 11,932 (Top 0.3 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 11 years ago
  • Updated 8 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A lightweight library that provides tools for organizing asynchronous code

RSVP.js Build Status Inline docs

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

CDN

<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

yarn add --save rsvp
# or ...
npm install --save rsvp

RSVP.Promise is an implementation of Promises/A+ that passes the test suite.

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, 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

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:

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.

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:

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:

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.

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.

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

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.

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.

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().

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:

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

var deferred = RSVP.defer();
// ...
deferred.promise // access the promise
// ...
deferred.resolve();

TaskJS

The TaskJS library makes it possible to take promises-oriented code and make it synchronous using ES6 generators.

Let's review an earlier example:

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:

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

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.

More Repositories

1

helix

Native Ruby extensions without fear
Rust
1,980
star
2

htmlbars

A variant of Handlebars that emits DOM and allows you to write helpers that manipulate live DOM nodes
JavaScript
1,607
star
3

router.js

TypeScript
1,348
star
4

route-recognizer

A lightweight JavaScript library that matches paths against registered routes. It includes support for dynamic and star segments and nested handlers.
TypeScript
461
star
5

bloggr-client

The Source for the Ember Get Excited Video
JavaScript
310
star
6

oasis.js

A lightweight library for embedding untrusted content and exposing capabilities. I guess it does two things? But it does them well.
JavaScript
281
star
7

conductor.js

JavaScript
150
star
8

simple-html-tokenizer

A lightweight JavaScript library for tokenizing non-`<script>` HTML expected to be found in the `<body>` of a document
TypeScript
85
star
9

learning-rust

Rust
67
star
10

kamino.js

Kamino.js is a library for passing data structures between sandboxed environments in the browser via `postMessage`.
JavaScript
48
star
11

ember-element-helper

Dynamic element helper for Glimmer templates.
JavaScript
42
star
12

ember-async-await-helper

Awaits a promise, then yields its result to a block. 👌
JavaScript
40
star
13

slackathon

A simple way to build slack interations inside a Rails app.
Ruby
31
star
14

MessageChannel.js

JavaScript
27
star
15

bound-templates.js

Bound Templates provides an interface similar to Polymer's MDV less invasively on more browsers (by using wrapper objects and an HTML parser written in JavaScript)
JavaScript
25
star
16

broccoli-typescript-compiler

TypeScript
24
star
17

json-normalizer

A small, lightweight library for normalizing JSON properties and values.
JavaScript
24
star
18

helix-rails

Helix for Rails
Ruby
22
star
19

libkit

JavaScript
17
star
20

ts-std

A standard library for TypeScript, extracted from Glimmer and other Tilde projects
TypeScript
10
star
21

buffoon

A protobuf library for Rust
Rust
10
star
22

selector-generator

A lightweight JavaScript library that generates CSS selectors from DOM nodes.
JavaScript
10
star
23

canal.js

Canal.js is a lightweight JavaScript library for creating channels, which are objects that represent a stream of events.
JavaScript
10
star
24

ember-control-flow-component

The superclass for all your "control-flow" component needs
9
star
25

ember-women

The Ember Women Helping Women Program
CSS
8
star
26

libcruby-sys

Rust
8
star
27

helix-website

Website for the Helix project
Ruby
8
star
28

kafka-librato-reporter

Report Kafka metrics to Librato
Java
8
star
29

sync.js

JavaScript
6
star
30

range-serializer

A lightweight JavaScript library that serializes/deserializes DOM range objects.
JavaScript
6
star
31

helix-flipper

A simple Rails app to get you started with Helix
Ruby
5
star
32

jsframe.js

Convert JavaScript to (near) JavaScript/HTML polyglot.
JavaScript
4
star
33

oasis-website

Oasis.js helps you structure communication between multiple untrusted sandboxes
CSS
3
star
34

ember-template-string-interpolation

JavaScript
3
star
35

rustconf

Annual Rust Conference
HTML
3
star
36

ember-training-june-day2

JavaScript
3
star
37

discourse-refcount-resource-spike

JavaScript
2
star
38

skiplist.js

JavaScript
2
star
39

ember-training-nyc-june

Material for the Tilde training course in NYC
JavaScript
2
star
40

emberconf-meetups

A list of unofficial EmberConf meetups
2
star
41

ember-swappable-service

JavaScript
2
star
42

ember-better-attributes

An Ember plugin that provides a better attribute syntax, building on simple-html-tokenizer. It is expected to be merged into Ember in the future.
JavaScript
2
star
43

fastboot-test

JavaScript
1
star
44

rails_todo_app

Ruby
1
star
45

ruby-rust-musl

Test of building musl Rust extensions for Ruby
Ruby
1
star