Reputation: 83680
What are benefits of using Deferrable instead of callbacks. Small example.
# This is Deferrable interface
request = NetworkIO.get
request.callback do |resp|
puts "Job is done"
end
request.errback do |err|
puts "Oh. My name is Forest, Forest Gump"
end
# And this is callback interface
NetworkIO.get do |resp|
if Exception === resp
puts "Oh. My name is Forest, Forest Gump"
else
puts "Job is done!"
end
end
# Or node js style
NetworkIO.get do |err, resp|
if err
puts "Oh. My name is Forest, Forest Gump"
else
puts "Job is done!"
end
end
Deferrable has got two nesting levels, while callbacks has got three of them.
As I can see, with Deferrable object you can pass errback, for example, and define only callback. Which is have sense in some cases, so code become more readable with less lines of code and less nesting.
But. I've found one annoying case. For example you have got this fake async API.
class SomeIO
def find(msg)
response = DeferrableResponse.new
req = socket.send(msg)
req.callback do |data|
response.succeed(data)
end
req.errback do |err|
response.fail(err)
end
response
end
def count(msg)
response = DeferrableResponse.new
req = find(msg)
req.callback do |data|
response.succeed(data.size)
end
req.errback do |err|
response.fail(err)
end
response
end
end
How should it looks in callback mode
class SomeIO
def find(msg, &blk)
socket.send(msg) do |resp|
blk.call(resp)
end
end
def count(msg, &blk)
find(msg) do |resp|
if Exception === resp
blk.call(resp)
else
cnt = resp.size
blk.call(cnt)
end
end
end
end
Even now it looks a bit cleaner (for my taste). But it is appreciation subjective. Imagine, that you are going to support synchronouse API over your asynchronouse one. With Deferrable Interface you should wrap all your methods with deferrable responses into Fiber (which is very big work and very heavy to support), while in callback you have to call Fibers only on trully async ops:
class SyncSomeIO < SomeIO
def find(msg, &blk)
fib = Fiber.current
socket.send(msg) do |resp|
fib.resume(resp)
end
res = Fiber.yield
raise res if res === Exception
res
end
end
So you've just wrapped your socket in Fiber and your async code became synchronouse. Trully to said you should also redefine all your block methods:
class SomeIO
...
def count(msg, &blk)
find(msg) do |resp|
if Exception === resp
block_given? ? blk.call(resp) : resp
else
cnt = resp.size
block_given? ? blk.call(cnt) : cnt
end
end
end
end
It is minor change, but in just few lines of code your API could work both in sync and async modes.
Sorry for such a big introduction and thank you for reading (all of you two guys).
The question is. Deferrable is de-facto is a standard of evented api in Ruby. So maybe I misunderstanding something, and I use Deferrable wrong way? Maybe callback interfaces smells and have got some bad issues?
PS: I wrote all this because I am working on MongoDB driver on EventMachine and now adding synchronouse interface to client. And eventually realized that I should monkey patch all public API to add sync support because of Deferrables and thinking about to rewrite it on callbacks.
Upvotes: 4
Views: 378
Reputation: 23225
If you control the Deferrable
's interface, you could do something along the lines of:
class DeferrableResponse
def sync
fiber = Fiber.current
callback { |val| fiber.resume(val) }
errback { |err| fiber.resume(err) }
result = Fiber.yield
raise result if result.is_a? Exception
result
end
end
This will let you use the synchronous API like this:
response = NeworkIO.get.sync
Upvotes: 1