Module: EventMachine::IMAP::ListeningDeferrable
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
-
#listen(&block) ⇒ Object
Register a block to be called when receive_event is called.
-
#receive_event(*args, &block) ⇒ Object
Pass arguments onto any blocks registered with listen.
-
#stop(*args, &block) ⇒ Object
Initiate shutdown.
-
#stopback(&block) ⇒ Object
Register a block to be called when the ListeningDeferrable is stopped.
-
#transform(&block) ⇒ Object
A re-implementation of DG::Combinators#transform.
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 |