lib/ably/modules/state_emitter.rb in ably-0.6.2 vs lib/ably/modules/state_emitter.rb in ably-0.7.0

- old
+ new

@@ -24,10 +24,11 @@ # connection.connecting? # => true # connection.state # => STATE.Connecting # connection.state = :invalid # raises an Exception as only a valid state can be defined # connection.trigger :invalid # raises an Exception as only a valid state can be used for EventEmitter # connection.change_state :connected # emits :connected event via EventEmitter, returns STATE.Connected + # connection.once_or_if(:connected) { puts 'block called once when state is connected or becomes connected' } # module StateEmitter # Current state {Ably::Modules::Enum} # # @return [Symbol] state @@ -53,10 +54,85 @@ trigger @state, *args end end alias_method :change_state, :state= + # If the current state matches the target_state argument the block is called immediately. + # Else the block is called once when the target_state is reached. + # + # If the option block :else is provided then if any state other than target_state is reached, the :else block is called, + # however only one of the blocks will ever be called + # + # @param [Symbol,Ably::Modules::Enum,Array] target_states a single state or array of states that once met, will fire the success block only once + # @param [Hash] options + # @option options [Proc] :else block called once the state has changed to anything but target_state + # + # @yield block is called if the state is matched immediately or once when the state is reached + # + # @return [void] + def once_or_if(target_states, options = {}, &success_block) + raise ArgumentError, 'Block is expected' unless block_given? + + if Array(target_states).any? { |target_state| state == target_state } + success_block.call + else + failure_block = options.fetch(:else, nil) + failure_wrapper = nil + + success_wrapper = Proc.new do + success_block.call + off &success_wrapper + off &failure_wrapper if failure_wrapper + end + + failure_wrapper = proc do |*args| + failure_block.call *args + off &success_wrapper + off &failure_wrapper + end if failure_block + + Array(target_states).each do |target_state| + once target_state, &success_wrapper + + once_state_changed do |*args| + failure_wrapper.call *args unless state == target_state + end if failure_block + end + end + end + + # Calls the block once when the state changes + # + # @yield block is called once the state changes + # @return [void] + # + # @api private + def once_state_changed(&block) + raise ArgumentError, 'Block is expected' unless block_given? + + once_block = proc do |*args| + off *self.class::STATE.map, &once_block + yield *args + end + + once *self.class::STATE.map, &once_block + end + private + + # Returns an {EventMachine::Deferrable} and once the target state is reached, the + # success_block if provided and {EventMachine::Deferrable#callback} is called. + # If the state changes to any other state, the {EventMachine::Deferrable#errback} is called. + # + def deferrable_for_state_change_to(target_state, &success_block) + EventMachine::DefaultDeferrable.new.tap do |deferrable| + once_or_if(target_state, else: proc { |*args| deferrable.fail self, *args }) do + success_block.call self if block_given? + deferrable.succeed self + end + end + end + def self.included(klass) klass.configure_event_emitter coerce_into: Proc.new { |event| if event == :error :error else