lgierth/promise.rb

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# promise.rb [![Build Status](https://travis-ci.org/lgierth/promise.rb.png?branch=master)](https://travis-ci.org/lgierth/promise.rb) [![Code Climate](https://codeclimate.com/github/lgierth/promise.rb.png)](https://codeclimate.com/github/lgierth/promise.rb) [![Coverage Status](https://coveralls.io/repos/lgierth/promise.rb/badge.png?branch=master)](https://coveralls.io/r/lgierth/promise.rb?branch=master)

Ruby implementation of the [Promises/A+ spec](http://promisesaplus.com/).
100% [mutation coverage](https://github.com/mbj/mutant),
tested on MRI 1.9, 2.0, 2.1, 2.2, Rubinius, and JRuby.

Similar projects:

- [concurrent-ruby](https://github.com/jdantonio/concurrent-ruby), Promises/A(+) inspired implementation, thread based
- [ruby-thread](https://github.com/meh/ruby-thread), thread/mutex/condition variable based, thread safe
- [promise](https://github.com/bhuga/promising-future), a.k.a. promising-future, classic promises and futures, thread based
- [celluloid-promise](https://github.com/cotag/celluloid-promise), inspired by Q, backed by a Celluloid actor
- [em-promise](https://github.com/cotag/em-promise), inspired by Q, backed by an EventMachine reactor
- [futuristic](https://github.com/seanlilmateus/futuristic), MacRuby bindings for Grand Central Dispatch
- [methodmissing/promise](https://github.com/methodmissing/promise), thread based, abandoned

*Note that promise.rb is probably not thread safe.*

## Installation

Add this line to your application's Gemfile:

    gem 'promise.rb'

And then execute:

    $ bundle

Or install it yourself as:

    $ gem install promise.rb

## Usage

This guide assumes that you are familiar with the [Promises/A+ spec](http://promisesaplus.com/). It's a quick read, though.

promise.rb comes with a very primitive way of scheduling callback dispatch. It
immediately executes the callback, instead of scheduling it for execution
*after* `Promise#fulfill` or `Promise#reject`, as demanded by the spec:

> onFulfilled or onRejected must not be called until the execution context
> stack contains only platform code.

Compliance can be achieved, for example, by running an event reactor like
EventMachine:

```ruby
require 'promise'
require 'eventmachine'

class MyPromise < Promise
  def defer
    EM.next_tick { yield }
  end
end
```

Now you can create MyPromise objects, and fulfill (or reject) them, as well as
add callbacks to them:

```ruby
def nonblocking_stuff
  promise = MyPromise.new
  EM.next_tick { promise.fulfill('value') }
  promise
end

EM.run do
  nonblocking_stuff.then { |value| p value }
  nonblocking_stuff.then(proc { |value| p value })
end
```

Rejection works similarly:

```ruby
def failing_stuff
  promise = MyPromise.new
  EM.next_tick { promise.reject('reason') }
  promise
end

EM.run do
  failing_stuff.then(proc { |value| }, proc { |reason| p reason })
end
```

### Waiting for fulfillment/rejection

promise.rb also comes with the utility method `Promise#sync`, which waits for
the promise to be fulfilled and returns the value, or for it to be rejected and
re-raises the reason. Using `#sync` requires you to implement `#wait`. You could
for example cooperatively schedule fibers waiting for different promises:

```ruby
require 'fiber'
require 'promise'
require 'eventmachine'

class MyPromise < Promise
  def defer
    EM.next_tick { yield }
  end

  def wait
    fiber = Fiber.current
    resume = proc do |arg|
      defer { fiber.resume(arg) }
    end

    self.then(resume, resume)
    Fiber.yield
  end
end

EM.run do
  promise = MyPromise.new
  Fiber.new { p promise.sync }.resume
  promise.fulfill
end
```

Or have the rejection reason re-raised from `#sync`:

```ruby
EM.run do
  promise = MyPromise.new

  Fiber.new do
    begin
      promise.sync
    rescue
      p $!
    end
  end.resume

  promise.reject('reason')
end
```

### Chaining promises

As per the A+ spec, every call to `#then` returns a new promise, which assumes
the first promise's state. That means it passes its `#fulfill` and `#reject`
methods to first promise's `#then`, short-circuiting the two promises. In case
a callback returns a promise, it'll instead assume that promise's state.

Imagine the `#fulfill` and `#reject` calls in the following example happening
somewhere in a background Fiber or so.

```ruby
require 'promise'

Promise.new
  .tap(&:fulfill)
  .then { Promise.new.tap(&:fulfill) }
  .then { Promise.new.tap(&:reject) }
  .then(nil, proc { |reason| p reason })
```

In order to use the result of multiple promises, they can be grouped using
`Promise.all` for chaining.

```ruby
sum_promise = Promise.all([promise1, promise2]).then do |value1, value2|
  value1 + value2
end
```

### Progress callbacks

Very simple progress callbacks, as per Promises/A, are supported as well. They have been dropped in A+, but I found them to be a useful mechanism - if kept simple. Callback dispatch happens immediately in the call to `#progress`, in the order of definition via `#on_progress`. Also note that `#on_progress` does not return a new promise for chaining - the progress mechanism is meant to be very lightweight, and ignores many of the constraints and guarantees of `then`.

```ruby
promise = MyPromise.new
promise.on_progress { |status| p status }
promise.progress(:anything)
```

## Unlicense

promise.rb is free and unencumbered public domain software. For more
information, see [unlicense.org](http://unlicense.org/) or the accompanying
UNLICENSE file.

## Contributing

1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request