Deferrable

Event::Machine’s Deferrable borrows heavily from the deferred object in Python’s Twisted event-handling framework. Here’s a minimal example that illustrates Deferrable:

 require 'eventmachine'

 class MyClass
   include EM::Deferrable

   def print_value x
     puts "MyClass instance received #{x}"
   end
 end

 EM.run {
   df = MyClass.new
   df.callback {|x|
     df.print_value(x)
     EM.stop
   }

   EM::Timer.new(2) {
     df.set_deferred_status :succeeded, 100
   }
 }

This program will spin for two seconds, print out the string MyClass instance received 100 and then exit.

The Deferrable pattern allows you to specify any number of Ruby code blocks (callbacks or errbacks) that will be executed at some future time when the status of the Deferrable object changes.

How might that be useful?

  1. Well, imagine that you’re implementing an HTTP server,
  2. but you need to make a call to some other server in order to fulfill a client request.
  3. When you receive a request from one of your clients, you can create and return a Deferrable object.
  4. Some other section of your program can add a callback to the Deferrable that will cause the client’s request to be fulfilled.
  5. Simultaneously, you initiate an event-driven or threaded client request to some different server.
  6. And then your EM program will continue to process other events and service other client requests.
  7. When your client request to the other server completes some time later, you will call the set_deferred_status method on the Deferrable object, passing either a success or failure status, and an arbitrary number of parameters (which might include the data you received from the other server).
  8. At that point, the status of the Deferrable object becomes known, and its callback or errback methods are immediately executed.
  9. Callbacks and errbacks are code blocks that are attached to Deferrable objects at any time through the methods callback and errback.

  10. The deep beauty of this pattern is that it decouples the disposition of one operation (such as a client request to an outboard server) from the subsequent operations that depend on that disposition (which may include responding to a different client or any other operation).

  11. The code which invokes the deferred operation (that will eventually result in a success or failure status together with associated data) is completely separate from the code which depends on that status and data.

  12. This achieves one of the primary goals for which threading is typically used in sophisticated applications, with none of the nondeterminacy or debugging difficulties of threads.

  13. As soon as the deferred status of a Deferrable becomes known by way of a call to set_deferred_status, the Deferrable will IMMEDIATELY execute all of its callbacks or errbacks in the order in which they were added to the Deferrable.

  14. Callbacks and errbacks can be added to a Deferrable object at any time, not just when the object is created. They can even be added after the status of the object has been determined! (In this case, they will be executed immediately when they are added.)

  15. A call to Deferrable#set_deferred_status takes :succeeded or :failed as its first argument. (This determines whether the object will call its callbacks or its errbacks.) set_deferred_status also takes zero or more additional parameters, that will in turn be passed as parameters to the callbacks or errbacks.

  16. In general, you can only call set_deferred_status ONCE on a Deferrable object. A call to set_deferred_status will not return until all of the associated callbacks or errbacks have been called. If you add callbacks or errbacks AFTER making a call to set_deferred_status, those additional callbacks or errbacks will execute IMMEDIATELY. Any given callback or errback will be executed AT MOST once.

  17. It’s possible to call set_deferred_status AGAIN, during the execution a callback or errback. This makes it possible to change the parameters which will be sent to the callbacks or errbacks farther down the chain, enabling some extremely elegant use-cases. You can transform the data returned from a deferred operation in arbitrary ways as needed by subsequent users, without changing any of the code that generated the original data.

  18. A call to set_deferred_status will not return until all of the associated callbacks or errbacks have been called. If you add callbacks or errbacks AFTER making a call to set_deferred_status, those additional callbacks or errbacks will execute IMMEDIATELY.

[~/ruby/eventmachine/deferrable(master)]$ cat defaultdeferrable.rb 
require 'eventmachine'
EM.run do
 df = EM::DefaultDeferrable.new
   df.callback do |x|
     puts "got #{x}"
   end
   df.callback do |x|
     EM.stop
   end
   EM.add_timer(1) do
     df.set_deferred_status :succeeded, "monkeys"
   end
end
DefaultDeferrable is an otherwise empty class that includes Deferrable.

This is useful when you just need to return a Deferrable object as a way of communicating deferred status to some other part of a program.

[~/ruby/eventmachine/deferrable(master)]$ ruby defaultdeferrable.rb 
got monkeys

Casiano Rodriguez León 2015-01-07