I needed several classes with methods performing network requests which should be executed asynchronously (with callbacks). To get rid of repetition, I added a mixin and a helper class:
module Asyncable
def async(&callback)
AsyncHelper.new(self, &callback)
end
end
class AsyncHelper
attr_reader :base, :callback, :errback
def initialize(base, &callback)
@base = base
@callback = callback
@errback = nil
end
def onerror!(&errback)
@errback = errback
self
end
def method_missing(sym, *args)
method = @base.method(sym)
Thread.new do
begin
res = method.call(*args)
rescue => e
if @errback.nil?
raise
else
@errback.call(e)
end
else
unless @callback.nil?
@callback.call(res)
end
res
end
end
end
end
Now all methods in classes can be implemented as synchronous, and then used in these ways:
# assuming base is an instance of a class including Asyncable
# sequential execution of a method:
value = base.method(params)
f(value) # process the value
# same method executed asynchronously, without callback:
thread = base.async.method(params)
# ... other operations while the method is executed in the background ...
value = thread.value # blocks until completed and raises errors, if any
f(value)
# same method executed asynchronously, with callback:
thread = base.async { |value| f(value) }.method(params)
# ... other operations while the method is executed in the background ...
thread.join # blocks until completed and raises errors, if any
# using errback to handle errors:
thread = base.async { |value| f(value) }.onerror! { |e| puts e }.method(params)
# ... other operations while the method is executed in the background ...
thread.join # only blocks, no errors raised here
This works perfectly for me, but I am a bit concerned by the fact that such an approach isn't widely used (I haven't seen it anywhere), while the use-case for it may be common enough. Is it a good way to approach the task, and how can be improved?
futures
orpromises
. – aplavin Mar 23 at 17:37