module EventMachine::Deferrable

def self.future arg, cb=nil, eb=nil, &blk

Then return arg.
use the supplied block (if any) as the callback.
are defined, then use them. If neither are defined, then
then look at the arguments. If either callback or errback
If arg is deferrable (responds to :set_deferred_status),
If arg is an ordinary expression, then return it.
What's the class of arg?
Evaluate arg (which may be an expression or a block).
--
A future is a sugaring of a typical deferrable usage.
def self.future arg, cb=nil, eb=nil, &blk
  arg = arg.call if arg.respond_to?(:call)
  if arg.respond_to?(:set_deferred_status)
    if cb || eb
      arg.callback(&cb) if cb
      arg.errback(&eb) if eb
    else
      arg.callback(&blk) if blk
    end
  end
  arg
end

def callback &block


If status is failed, do nothing.
If status is succeeded, execute the callback immediately.
If there is no status, add a callback to an internal list.
--

prior #set_deferred_status call.
block will be executed immediately, receiving the parameters given to the
If you call this method on a Deferrable whose status is :succeeded, the
will cause the callback block to be stored on an internal list.
Calling this method on a Deferrable object whose status is not yet known

a status of :succeeded. See #set_deferred_status for more information.
Specify a block to be executed if and when the Deferrable object receives
def callback &block
  return unless block
  @deferred_status ||= :unknown
  if @deferred_status == :succeeded
    block.call(*@deferred_args)
  elsif @deferred_status != :failed
    @callbacks ||= []
    @callbacks.unshift block # << block
  end
  self
end

def cancel_callback block


Cancels an outstanding callback to &block if any. Undoes the action of #callback.
def cancel_callback block
  @callbacks ||= []
  @callbacks.delete block
end

def cancel_errback block


Cancels an outstanding errback to &block if any. Undoes the action of #errback.
def cancel_errback block
  @errbacks ||= []
  @errbacks.delete block
end

def cancel_timeout


Cancels an outstanding timeout if any. Undoes the action of #timeout.
def cancel_timeout
  @deferred_timeout ||= nil
  if @deferred_timeout
    @deferred_timeout.cancel
    @deferred_timeout = nil
  end
end

def errback &block


If status is succeeded, do nothing.
If status is failed, execute the errback immediately.
If there is no status, add an errback to an internal list.
--
a status of :failed. See #set_deferred_status for more information.
Specify a block to be executed if and when the Deferrable object receives
def errback &block
  return unless block
  @deferred_status ||= :unknown
  if @deferred_status == :failed
    block.call(*@deferred_args)
  elsif @deferred_status != :succeeded
    @errbacks ||= []
    @errbacks.unshift block # << block
  end
  self
end

def fail *args


Sugar for set_deferred_status(:failed, ...)
def fail *args
  set_deferred_status :failed, *args
end

def set_deferred_status status, *args


comments under the new method #timeout.
if a Deferrable was timed out and then an attempt was made to succeed it. See the
within a callback or errback, but more seriously it would cause spurious errors
behavior would invalidate the idiom of resetting arguments by setting status from
to raise an exception is status is set more than once on a Deferrable. The latter
triggering its errbacks! That is clearly undesirable, but it's just as undesirable
to "succeed" a Deferrable (triggering its callbacks), and then immediately "fail" it,
which only allows status to be set once. Prior to making this change, it was possible
handlers. This gets us a little closer to the behavior of Twisted's "deferred,"
Changed 15Sep07: after processing callbacks or errbacks, CLEAR the other set of

versions.
by Kirk Haines, to work around the memory leak bug that still exists in many Ruby
Changed @callbacks and @errbacks from push/shift to unshift/pop, per suggestion

that will be sent to subsequent callbacks down the chain.
means that a callback can call #set_deferred_status and change the parameters
once. It also has the magic effect of permitting recursive calls, which
This is valid because by definition callbacks are executed no more than
We're shifting callbacks off and discarding them as we execute them.
--

an ArgumentError.
errbacks match the arguments given in calls to #set_deferred_status, otherwise Ruby will raise
It's your responsibility to ensure that the argument lists specified in your callbacks and
they will be passed as arguments to any callbacks or errbacks that are executed.
If you pass any arguments to #set_deferred_status in addition to the status argument,

passed to the object using # callback will be discarded.
method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks
If you pass :failed, then all of the blocks passed to the object using the #errback

passed to the object using #errback will be discarded.
method (if any) will be executed BEFORE the #set_deferred_status method returns. All of the blocks
If you pass :succeeded, then all of the blocks passed to the object using the #callback

You may give either :succeeded or :failed as the status argument.

ones.
callback wants to change the parameters that will be passed to subsequently-registered
on the INSIDE of a callback. This is very useful when a previously-registered
OBSERVE SOMETHING VERY SPECIAL here: you may call this method even

document the arguments they will supply to user callbacks.
Implementors of deferrable classes must
user code will throw an argument exception.
If the user has coded these with arguments, then the
no arguments will be passed to the callback/errback.
Note that if you call this method without arguments,
sugarings for this method.
Sets the "disposition" (status) of the Deferrable object. See also the large set of
def set_deferred_status status, *args
  cancel_timeout
  @errbacks ||= nil
  @callbacks ||= nil
  @deferred_status = status
  @deferred_args = args
  case @deferred_status
  when :succeeded
    if @callbacks
      while cb = @callbacks.pop
        cb.call(*@deferred_args)
      end
    end
    @errbacks.clear if @errbacks
  when :failed
    if @errbacks
      while eb = @errbacks.pop
        eb.call(*@deferred_args)
      end
    end
    @callbacks.clear if @callbacks
  end
end

def succeed *args


Sugar for set_deferred_status(:succeeded, ...)
def succeed *args
  set_deferred_status :succeeded, *args
end

def timeout seconds, *args

will cause the timer to be cancelled.
Setting the status at any time prior to a call to the expiration of the timeout
the Timeout expires (passing no arguments to the object's errbacks).
Setting a timeout on a Deferrable causes it to go into the failed state after
def timeout seconds, *args
  cancel_timeout
  me = self
  @deferred_timeout = EventMachine::Timer.new(seconds) {me.fail(*args)}
  self
end