class Tilia::Event::Promise

An implementation of the Promise pattern.

Promises basically allow you to avoid what is commonly called 'callback hell'. It allows for easily chaining of asynchronous operations.

Constants

FULFILLED

The promise has been fulfilled. It was successful.

PENDING

Pending promise. No result yet.

REJECTED

The promise was rejected. The operation failed.

Public Class Methods

all(promises) click to toggle source

It's possible to send an array of promises to the all method. This method returns a promise that will be fulfilled, only if all the passed promises are fulfilled.

@param [Array<Promise>] promises @return [Promise]

# File lib/tilia/event/promise.rb, line 116
def self.all(promises)
  new(
    lambda do |success, failing|
      success_count = 0
      complete_result = []

      promises.each_with_index do |sub_promise, promise_index|
        sub_promise.then(
          lambda do |result|
            complete_result[promise_index] = result
            success_count += 1

            success.call(complete_result) if success_count == promises.size

            return result
          end
        ).error(
          lambda do |reason|
            failing.call(reason)
          end
        )
      end
    end
  )
end
new(executor = nil) click to toggle source

Creates the promise.

The passed argument is the executor. The executor is automatically called with two arguments.

Each are callbacks that map to self.fulfill and self.reject. Using the executor is optional.

@param [#call, nil] executor @return [void]

# File lib/tilia/event/promise.rb, line 27
def initialize(executor = nil)
  @state = PENDING
  @subscribers = []
  @value = nil

  executor.call(method(:fulfill), method(:reject)) if executor
end

Public Instance Methods

error(on_rejected) click to toggle source

Add a callback for when this promise is rejected.

I would have used the word 'catch', but it's a reserved word in PHP, so we're not allowed to call our function that.

@param [#call] on_rejected @return [Promise]

# File lib/tilia/event/promise.rb, line 76
def error(on_rejected)
  self.then(nil, on_rejected)
end
fulfill(value = nil) click to toggle source

Marks this promise as fulfilled and sets its return value.

@param value @return [void]

# File lib/tilia/event/promise.rb, line 84
def fulfill(value = nil)
  unless @state == PENDING
    fail PromiseAlreadyResolvedException, 'This promise is already resolved, and you\'re not allowed to resolve a promise more than once'
  end
  @state = FULFILLED
  @value = value
  @subscribers.each do |subscriber|
    invoke_callback(subscriber[0], subscriber[1])
  end
end
reject(reason = nil) click to toggle source

Marks this promise as rejected, and set it's rejection reason.

@param reason @return [void]

# File lib/tilia/event/promise.rb, line 99
def reject(reason = nil)
  unless @state == PENDING
    fail PromiseAlreadyResolvedException, 'This promise is already resolved, and you\'re not allowed to resolve a promise more than once'
  end
  @state = REJECTED
  @value = reason
  @subscribers.each do |subscriber|
    invoke_callback(subscriber[0], subscriber[2])
  end
end
then(on_fulfilled = nil, on_rejected = nil) click to toggle source

This method allows you to specify the callback that will be called after the promise has been fulfilled or rejected.

Both arguments are optional.

This method returns a new promise, which can be used for chaining. If either the onFulfilled or onRejected callback is called, you may return a result from this callback.

If the result of this callback is yet another promise, the result of that promise will be used to set the result of the returned promise.

If either of the callbacks return any other value, the returned promise is automatically fulfilled with that value.

If either of the callbacks throw an exception, the returned promise will be rejected and the exception will be passed back.

@param [#call, nil] on_fulfilled @param [#call, nil] on_rejected @return [Promise]

# File lib/tilia/event/promise.rb, line 56
def then(on_fulfilled = nil, on_rejected = nil)
  sub_promise = Promise.new
  case @state
  when PENDING
    @subscribers << [sub_promise, on_fulfilled, on_rejected]
  when FULFILLED
    invoke_callback(sub_promise, on_fulfilled)
  when REJECTED
    invoke_callback(sub_promise, on_rejected)
  end
  sub_promise
end

Protected Instance Methods

invoke_callback(sub_promise, call_back = nil) click to toggle source

This method is used to call either an onFulfilled or onRejected callback.

This method makes sure that the result of these callbacks are handled correctly, and any chained promises are also correctly fulfilled or rejected.

@param [Promise] sub_promise @param [#call, nil] call_back @return [void]

# File lib/tilia/event/promise.rb, line 153
def invoke_callback(sub_promise, call_back = nil)
  if call_back.is_a?(Proc) || call_back.is_a?(Method)
    begin
      result = call_back.call(@value)
      if result.is_a?(Promise)
        result.then(sub_promise.method(:fulfill), sub_promise.method(:reject))
      else
        sub_promise.fulfill(result)
      end
    rescue => e
      sub_promise.reject(e.to_s)
    end
  elsif @state == FULFILLED
    sub_promise.fulfill(@value)
  else
    sub_promise.reject(@value)
  end
end