1
\$\begingroup\$

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?

\$\endgroup\$
  • \$\begingroup\$ Are you aware of EventMachine? github.com/igrigorik/em-http-request/wiki/Issuing-Requests \$\endgroup\$ – tokland Mar 23 '15 at 17:30
  • \$\begingroup\$ @tokland: I heard about it, but I had a requirement that calling with a callback specified should be supported. And my solution gives plain sequential execution 'for free', as well as async execution style similar to futures or promises. \$\endgroup\$ – aplavin Mar 23 '15 at 17:37
  • \$\begingroup\$ Have a look at Celluloid. \$\endgroup\$ – britishtea Mar 23 '15 at 19:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.