module BubbleWrap module Reactor # Provides a mixin for deferrable jobs. module Deferrable # def self.included(base) # base.extend ::BubbleWrap::Reactor::Future # end # Specify a block to be executed if and when the Deferrable object # receives a status of :succeeded. See set_deferred_status for more # information. # Calling this method on a Deferrable object whose status is not yet # known will cause the callback block to be stored on an internal # list. If you call this method on a Deferrable whose status is # :succeeded, the block will be executed immediately, receiving # the parameters given to the prior set_deferred_status call. def callback(&blk) return unless blk @deferred_status ||= :unknown if @deferred_status == :succeeded execute_block(&blk) elsif @deferred_status != :failed @callbacks ||= [] @callbacks.unshift blk end end # 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 # Specify a block to be executed if and when the Deferrable object # receives a status of :failed. See set_deferred_status for more # information. def errback(&blk) return unless blk @deferred_status ||= :unknown if @deferred_status == :failed execute_block(&blk) blk.call(*@deferred_args) elsif @deferred_status != :succeeded @errbacks ||= [] @errbacks.unshift blk end end def execute_block(&blk) return unless blk blk.call(*@deferred_args) end def delegate(delegate) callback_delegate(delegate) errback_delegate(delegate) self end def errback_delegate(delegate) errback do |*args| delegate.fail *args end self end def callback_delegate(delegate) callback do |*args| delegate.succeed *args end self end # Sugar for set_deferred_status(:failed, …) def fail(*args) set_deferred_status :failed, *args end alias set_deferred_failure fail # Sets the “disposition” (status) of the Deferrable object. See also # the large set of sugarings for this method. Note that if you call # this method without arguments, no arguments will be passed to the # callback/errback. If the user has coded these with arguments, # then the user code will throw an argument exception. Implementors # of deferrable classes must document the arguments they will supply # to user callbacks. # OBSERVE SOMETHING VERY SPECIAL here: you may call this method even # on the INSIDE of a callback. This is very useful when a # previously-registered callback wants to change the parameters that # will be passed to subsequently-registered ones. # You may give either :succeeded or :failed as the status argument. # If you pass :succeeded, then all of the blocks passed to the object # using the callback method (if any) will be executed BEFORE the # set_deferred_status method returns. All of the blocks passed to the # object using errback will be discarded. # If you pass :failed, then all of the blocks passed to the object # using the errback method (if any) will be executed BEFORE the # set_deferred_status method returns. All of the blocks passed to the # object using # callback will be discarded. # If you pass any arguments to set_deferred_status in addition to the # status argument, they will be passed as arguments to any callbacks # or errbacks that are executed. It’s your responsibility to ensure # that the argument lists specified in your callbacks and errbacks match # the arguments given in calls to set_deferred_status, otherwise Ruby # will raise an ArgumentError. 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 execute_block(&cb) end end @errbacks.clear if @errbacks when :failed if @errbacks while eb = @errbacks.pop execute_block(&eb) end end @callbacks.clear if @callbacks end end # Sugar for set_deferred_status(:succeeded, …) def succeed(*args) set_deferred_status :succeeded, *args end alias set_deferred_success succeed # Setting a timeout on a Deferrable causes it to go into the failed # state after the Timeout expires (passing no arguments to the object’s # errbacks). Setting the status at any time prior to a call to the # expiration of the timeout will cause the timer to be cancelled. def timeout(seconds) cancel_timeout me = self @deferred_timeout = Timer.new(seconds) {me.fail} end def deferred_status @deferred_status ||= :unknown end def deferred_args @deferred_args end end end end