class Rosarium::Promise

Public Class Methods

all(promises) click to toggle source
# File lib/rosarium/promise.rb, line 66
def self.all(promises)
  return resolve([]) if promises.empty?

  deferred = defer
  promises = promises.dup

  waiting_for = promises.count
  mutex = Mutex.new

  check = lambda do |promise|
    if promise.fulfilled?
      # Hits zero iff all promises were fulfilled
      if mutex.synchronize { (waiting_for -= 1) == 0 }
        deferred.resolve(promises.map(&:value))
      end
    else
      deferred.reject(promise.reason)
    end
  end

  promises.each do |promise|
    promise.send(:when_settled) { check.call(promise) }
  end

  deferred.promise
end
all_settled(promises) click to toggle source
# File lib/rosarium/promise.rb, line 43
def self.all_settled(promises)
  return resolve([]) if promises.empty?

  deferred = defer
  promises = promises.dup

  waiting_for = promises.count
  mutex = Mutex.new

  check = proc do
    # Includes both fulfilled and rejected, so always hits zero eventually
    if mutex.synchronize { (waiting_for -= 1) == 0 }
      deferred.resolve promises
    end
  end

  promises.each do |promise|
    promise.send(:when_settled, &check)
  end

  deferred.promise
end
defer() click to toggle source
# File lib/rosarium/promise.rb, line 8
def self.defer
  promise = new

  resolver = lambda do |value|
    promise.send(:try_settle, value, nil)
    nil # do not leak
  end

  rejecter = lambda do |reason|
    raise "reason must be an Exception" unless reason.is_a?(Exception)
    promise.send(:try_settle, nil, reason)
    nil # do not leak
  end

  Deferred.new(promise, resolver, rejecter)
end
execute(&block) click to toggle source
# File lib/rosarium/promise.rb, line 39
def self.execute(&block)
  @resolved.then(&block)
end
new() click to toggle source
# File lib/rosarium/promise.rb, line 93
def initialize
  @state = :pending
  @settling = false
  @mutex = Mutex.new
  @condition = ConditionVariable.new
  @when_settled = []
end
reject(reason) click to toggle source
# File lib/rosarium/promise.rb, line 33
def self.reject(reason)
  deferred = defer
  deferred.reject(reason)
  deferred.promise
end
resolve(value) click to toggle source
# File lib/rosarium/promise.rb, line 25
def self.resolve(value)
  return value if value.is_a? Promise

  deferred = defer
  deferred.resolve(value)
  deferred.promise
end

Public Instance Methods

catch(&block)
Alias for: rescue
fulfilled?() click to toggle source
# File lib/rosarium/promise.rb, line 132
def fulfilled?
  state == :fulfilled
end
inspect() click to toggle source
# File lib/rosarium/promise.rb, line 123
def inspect
  synchronize do
    r = { state: @state }
    r[:value] = @value if @state == :fulfilled
    r[:reason] = @reason if @state == :rejected
    r
  end
end
on_error(&block)
Alias for: rescue
reason() click to toggle source
# File lib/rosarium/promise.rb, line 110
def reason
  wait_until_settled
  synchronize { @reason }
end
rejected?() click to toggle source
# File lib/rosarium/promise.rb, line 136
def rejected?
  state == :rejected
end
rescue(&block) click to toggle source
# File lib/rosarium/promise.rb, line 164
def rescue(&block)
  self.then(block)
end
Also aliased as: catch, on_error
state() click to toggle source
# File lib/rosarium/promise.rb, line 101
def state
  synchronize { @state }
end
then(on_rejected = nil, &on_fulfilled) click to toggle source
# File lib/rosarium/promise.rb, line 140
def then(on_rejected = nil, &on_fulfilled)
  deferred = self.class.defer

  when_settled do
    EXECUTOR.submit do
      begin
        deferred.resolve(
          if fulfilled?
            # User-supplied code
            on_fulfilled ? on_fulfilled.call(value) : value
          else
            # User-supplied code
            on_rejected ? on_rejected.call(reason) : raise(reason)
          end
        )
      rescue Exception => e
        deferred.reject e
      end
    end
  end

  deferred.promise
end
value() click to toggle source
# File lib/rosarium/promise.rb, line 105
def value
  wait_until_settled
  synchronize { @value }
end
value!() click to toggle source
# File lib/rosarium/promise.rb, line 115
def value!
  wait_until_settled
  synchronize do
    raise @reason if @state == :rejected
    @value
  end
end

Protected Instance Methods

when_settled(&block) click to toggle source
# File lib/rosarium/promise.rb, line 228
def when_settled(&block)
  immediate = synchronize do
    @when_settled << block if @state == :pending
    @state != :pending
  end

  block.call if immediate
end

Private Instance Methods

settle(value, reason) click to toggle source

Only called once

# File lib/rosarium/promise.rb, line 216
def settle(value, reason)
  synchronize do
    @state = (reason ? :rejected : :fulfilled)
    @value = value
    @reason = reason
    @condition.broadcast
    @when_settled.slice!(0, @when_settled.length)
  end.each(&:call)
end
synchronize(&block) click to toggle source
# File lib/rosarium/promise.rb, line 182
def synchronize(&block)
  @mutex.synchronize(&block)
end
try_settle(value, reason) click to toggle source

Can be called more than once

# File lib/rosarium/promise.rb, line 187
def try_settle(value, reason)
  settle_with = nil

  synchronize do
    return if @state != :pending || @settling

    if value.is_a? Promise
      @settling = true
    elsif reason.nil?
      settle_with = [value, nil]
    else
      settle_with = [nil, reason]
    end
  end

  if settle_with
    settle(*settle_with)
  else
    value.when_settled do
      if value.fulfilled?
        settle(value.value, nil)
      else
        settle(nil, value.reason)
      end
    end
  end
end
wait_until_settled() click to toggle source
# File lib/rosarium/promise.rb, line 173
def wait_until_settled
  synchronize do
    loop do
      return if @state != :pending
      @condition.wait @mutex
    end
  end
end