lgierth/promise.rb
{ "createdAt": "2013-09-13T00:56:45Z", "defaultBranch": "master", "description": "Promises/A+ for Ruby", "fullName": "lgierth/promise.rb", "homepage": null, "language": "Ruby", "name": "promise.rb", "pushedAt": "2022-10-26T15:59:47Z", "stargazersCount": 373, "topics": [], "updatedAt": "2025-11-05T04:15:34Z", "url": "https://github.com/lgierth/promise.rb"}promise.rb

Section titled “promise.rb ”Ruby implementation of the Promises/A+ spec. 100% mutation coverage, tested on MRI 1.9, 2.0, 2.1, 2.2, Rubinius, and JRuby.
Similar projects:
- concurrent-ruby, Promises/A(+) inspired implementation, thread based
- ruby-thread, thread/mutex/condition variable based, thread safe
- promise, a.k.a. promising-future, classic promises and futures, thread based
- celluloid-promise, inspired by Q, backed by a Celluloid actor
- em-promise, inspired by Q, backed by an EventMachine reactor
- futuristic, MacRuby bindings for Grand Central Dispatch
- methodmissing/promise, thread based, abandoned
Note that promise.rb is probably not thread safe.
Installation
Section titled “Installation”Add this line to your application’s Gemfile:
gem 'promise.rb'And then execute:
$ bundleOr install it yourself as:
$ gem install promise.rbThis guide assumes that you are familiar with the Promises/A+ spec. 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:
require 'promise'require 'eventmachine'
class MyPromise < Promise def defer EM.next_tick { yield } endendNow you can create MyPromise objects, and fulfill (or reject) them, as well as add callbacks to them:
def nonblocking_stuff promise = MyPromise.new EM.next_tick { promise.fulfill('value') } promiseend
EM.run do nonblocking_stuff.then { |value| p value } nonblocking_stuff.then(proc { |value| p value })endRejection works similarly:
def failing_stuff promise = MyPromise.new EM.next_tick { promise.reject('reason') } promiseend
EM.run do failing_stuff.then(proc { |value| }, proc { |reason| p reason })endWaiting for fulfillment/rejection
Section titled “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:
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 endend
EM.run do promise = MyPromise.new Fiber.new { p promise.sync }.resume promise.fulfillendOr have the rejection reason re-raised from #sync:
EM.run do promise = MyPromise.new
Fiber.new do begin promise.sync rescue p $! end end.resume
promise.reject('reason')endChaining promises
Section titled “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.
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.
sum_promise = Promise.all([promise1, promise2]).then do |value1, value2| value1 + value2endProgress callbacks
Section titled “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.
promise = MyPromise.newpromise.on_progress { |status| p status }promise.progress(:anything)Unlicense
Section titled “Unlicense”promise.rb is free and unencumbered public domain software. For more information, see unlicense.org or the accompanying UNLICENSE file.
Contributing
Section titled “Contributing”- Fork it
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create new Pull Request