class PurePromise

This coerces a thenable into a PurePromise I wanted to keep this separate because there are a lot of edge cases that need handling if the thenable doesn't conform to the spec properly.

Constants

MutationError

Public Class Methods

error(message_or_exception=nil, message=nil, backtrace=nil) click to toggle source

TODO: Clean this up, it's pretty messy.

# File lib/pure_promise.rb, line 14
def error(message_or_exception=nil, message=nil, backtrace=nil)
  backtrace ||= caller(2) # Fix for jRuby - See https://github.com/jruby/jruby/issues/1908
  if message_or_exception.respond_to?(:exception)
    exception = message_or_exception.exception(message || message_or_exception)
  else
    exception = RuntimeError.new(message_or_exception)
  end
  exception.set_backtrace(backtrace)
  reject(exception)
end
new() { |method(:fulfill), method(:reject)| ... } click to toggle source
# File lib/pure_promise.rb, line 26
def initialize
  @state = :pending # Pending/fulfilled/rejected
  @callbacks = []

  yield method(:fulfill), method(:reject) if block_given?
end

Public Instance Methods

catch(&block) click to toggle source
# File lib/pure_promise.rb, line 44
def catch(&block)
  self.then(null_callback, block || null_callback)
end
fulfill(value=nil) click to toggle source
# File lib/pure_promise.rb, line 48
def fulfill(value=nil)
  mutate_state(:fulfilled, value, @callbacks.map(&:first))
end
reject(value=nil) click to toggle source
# File lib/pure_promise.rb, line 52
def reject(value=nil)
  mutate_state(:rejected, value, @callbacks.map(&:last))
end
resolve(promise) click to toggle source

TODO: Rename method to: receive? acquire? take?

# File lib/pure_promise.rb, line 60
def resolve(promise)
  if equal?(promise)
    raise TypeError, 'Promise cannot be resolved to itself'
  elsif Coercer.is_thenable?(promise)
    Coercer.coerce(promise, self.class).resolve_into(self)
    self
  else
    raise TypeError, 'Argument is not a promise'
  end
end
resolve_into(pure_promise) click to toggle source
# File lib/pure_promise.rb, line 71
def resolve_into(pure_promise)
  raise TypeError, 'Argument must be of same type as self' unless pure_promise.instance_of?(self.class)

  if fulfilled?
    pure_promise.fulfill(@value)
  elsif rejected?
    pure_promise.reject(@value)
  else
    self.then(pure_promise.method(:fulfill), pure_promise.method(:reject))
  end
  self
end
then(fulfill_callback=null_callback, reject_callback=null_callback, &block) click to toggle source

REVIEW: Consider having two callback chains, to avoid having potentially expensive null_callbacks littering @callbacks

# File lib/pure_promise.rb, line 34
def then(fulfill_callback=null_callback, reject_callback=null_callback, &block)
  fulfill_callback = block if block
  self.class.new.tap do |return_promise|
    register_callbacks(
        Callback.new(fulfill_callback, return_promise),
        Callback.new(reject_callback, return_promise)
    )
  end
end

Private Instance Methods

defer() { || ... } click to toggle source
# File lib/pure_promise.rb, line 86
def defer
  yield
end
mutate_state(state, value, callbacks) click to toggle source
# File lib/pure_promise.rb, line 90
def mutate_state(state, value, callbacks)
  raise MutationError, 'You can only mutate pending promises' unless pending?

  @state = state
  @value = value

  run_callbacks(callbacks)
  # TODO: Find a way of testing this - It makes no visible changes, apart from clearing some memory.
  @callbacks.clear

  self
end
null_callback() click to toggle source
# File lib/pure_promise.rb, line 125
def null_callback
  @null_callback ||= proc { self }
end
register_callbacks(fulfill_callback, reject_callback) click to toggle source
# File lib/pure_promise.rb, line 103
def register_callbacks(fulfill_callback, reject_callback)
  if fulfilled?
    defer { fulfill_callback.call(@value) }
  elsif rejected?
    defer { reject_callback.call(@value) }
  else
    @callbacks << [fulfill_callback, reject_callback]
  end
end
run_callbacks(callbacks) click to toggle source

This ensures that all callbacks run in order, by setting up an execution chain like proc { defer { a.call; proc { defer { b.call; … } }.call } }.call You might think this is really slow by only running one callback per tick, but here are some benchmarks with eventmachine: gist.github.com/cameron-martin/08abeaeae1bf746ef718

We do this because we do not want to require implementations of defer to execute blocks in the order they were registered.

# File lib/pure_promise.rb, line 119
def run_callbacks(callbacks)
  callbacks.reverse.inject(proc{}) do |memo, callback|
    proc { defer { callback.call(@value); memo.call } }
  end.call
end