module EventMachine::IMAP::ListeningDeferrable
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
Public Instance Methods
Register a block to be called when receive_event
is called.
# File lib/em-imap/listener.rb, line 93 def listen(&block) listeners << block self end
Pass arguments onto any blocks registered with listen.
# 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
Initiate shutdown.
# File lib/em-imap/listener.rb, line 112 def stop(*args, &block) stop_deferrable.succeed *args, &block end
Register a block to be called when the ListeningDeferrable
is stopped.
# File lib/em-imap/listener.rb, line 106 def stopback(&block) stop_deferrable.callback &block self end
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.
# 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
Private Instance Methods
# File lib/em-imap/listener.rb, line 145 def listeners; @listeners ||= []; end
# File lib/em-imap/listener.rb, line 146 def stop_deferrable; @stop_deferrable ||= DefaultDeferrable.new; end