Module: EventMachine::IMAP::ListeningDeferrable

Includes:
EM::Deferrable
Included in:
Listener
Defined in:
lib/em-imap/listener.rb

Overview

A Listener is a cancellable subscriber to an event stream, they are used to provide control-flow abstraction throughout em-imap.

They can be thought of as a deferrable with two internal phases:

deferrable: create |-------------------------------> [succeed/fail]
listener:   create |---[listening]----> [stop]-----> [succeed/fail]

A stopback may call succeed or fail immediately, or after performing necessary cleanup.

There are several hooks to which you can subscribe:

#listen(&block): Each time .receive_event is called, the block will
be called.

#stopback(&block): When someone calls .stop on the listener, this block
will be called.

#callback(&block), #errback(&block), #bothback(&block): Inherited from
deferrables (and enhanced by deferrable gratification).

And the corresponding methods for sending messages to subscribers:

#receive_event(*args): Passed onto blocks registered by listen.

#stop(*args): Calls all the stopbacks.

#succeed(*args), #fail(*args): Inherited from deferrables.

Listeners are defined in such a way that it's most natural to create them from deep within a library, and return them to the original caller via layers of abstraction.

To this end, they also have a .transform method which can be used to create a new listener that acts the same as the old listener, but which succeeds with a different return value. The call to .stop is propagated from the new listener to the old, but calls to .receive_event, .succeed and .fail are propagated from the old to the new.

This slightly contrived example shows how listeners can be used with three levels of abstraction juxtaposed:

def receive_characters

Listener.new.tap do |listener|

  continue = true
  listener.stopback{ continue = false }

  EM::next_tick do
    while continue
      if key = $stdin.read(1)
        listener.receive_event key
      else
        continue = false
        listener.fail EOFError.new
      end
    end
    listener.succeed
  end
end

end

def get_line

buffer = ""
listener = receive_characters.listen do |key|
  buffer << key
  listener.stop if key == "\n"
end.transform do
  buffer
end

end

EM::run do

get_line.callback do |line|
  puts "DONE: #{line}"
end.errback do |e|
  puts [e] + e.backtrace
end.bothback do
  EM::stop
end

end

Instance Method Summary collapse

Instance Method Details

#listen(&block) ⇒ Object

Register a block to be called when receive_event is called.


93
94
95
96
# File 'lib/em-imap/listener.rb', line 93

def listen(&block)
  listeners << block
  self
end

#receive_event(*args, &block) ⇒ Object

Pass arguments onto any blocks registered with listen.


99
100
101
102
103
# File 'lib/em-imap/listener.rb', line 99

def receive_event(*args, &block)
  # NOTE: Take a clone of listeners, so any listeners added by listen
  # blocks won't receive these events.
  listeners.clone.each{ |l| l.call *args, &block }
end

#stop(*args, &block) ⇒ Object

Initiate shutdown.


112
113
114
# File 'lib/em-imap/listener.rb', line 112

def stop(*args, &block)
  stop_deferrable.succeed *args, &block
end

#stopback(&block) ⇒ Object

Register a block to be called when the ListeningDeferrable is stopped.


106
107
108
109
# File 'lib/em-imap/listener.rb', line 106

def stopback(&block)
  stop_deferrable.callback &block
  self
end

#transform(&block) ⇒ Object

A re-implementation of DG::Combinators#transform.

The returned listener will succeed at the same time as this listener, but the value with which it succeeds will have been transformed using the given block. If this listener fails, the returned listener will also fail with the same arguments.

In addition, any events that this listener receives will be forwarded to the new listener, and the stop method of the new listener will also stop the existing listener.

NOTE: This does not affect the implementation of bind! which still returns a normal deferrable, not a listener.


130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/em-imap/listener.rb', line 130

def transform(&block)
  Listener.new.tap do |listener|
    self.callback do |*args|
      listener.succeed block.call(*args)
    end.errback do |*args|
      listener.fail *args
    end.listen do |*args|
      listener.receive_event *args
    end

    listener.stopback{ self.stop }
  end
end