class Perfume::Promise

Public: Very often we’re in a situation that we have to catch and eventuall log some errors, or even more, solely catch errors for purpose of logging. Other times we must perform multiple actions in different places according to the results produces by our method or service. We’d normally have to pass these results around deppening our stack trace. Here comes this a bit twisted implementation of a promise. Maybe twisted is wrong description, it’s rather simplified. It’s not parallel, it’s simple to the bones. It just allows you to define three kinds of callbacks: before call, on success and on failure of course. Since it inherits from our own Service, it essentially is one with own logging and access to class level call method.

Here’s few interesting properties:

  1. Define your logic in call! method. Yes call with bang.

    class FindWally < Perfume::Promise
      NotFoundWarning = Class.new(Warning)
    
      def call!
        Wally.latest_location.tap do |location|
          raise NotFoundWarning, "No idea where is Wally!" if location.nil?
        end
      end
    end
    
  2. Failures are dependent on the kind of error thrown. It means that the flow is directed by raised errors. Slow you might say, but benchmarking this shows that the overhead is negligible in real life apps. This also allows a nifty trick. Look at the example of ‘FindWally` above. Why there’s ‘NotFoundWarning` inhertiting from magical `Warning` class? The `Warning` is bundled into each new promise defined. This errors are not thrown like regular exceptions. Instead they’re handled and logged as warnings. This error will be passed to our ‘failure` callbacks as well. Check example:

    FindWally.call do |_location|
      raise StandardError, "Something went wrong!"
    end
    

    This one raises ‘StandardError`, while this one:

    find_wally = FindWally.new
    find_wally.failure { |err| puts err.message }
    find_wally.call { |_location| raise FindWally::Warning, "Uuups!" }
    

    Will log WARNING with “Uuups!” message.

  3. Block passed to call is added to on success callbacks:

    FindWally.call do |location|
      puts location
    end
    
    $ "Madrid"

Constants

Warning

Public Class Methods

inherited(child) click to toggle source
# File lib/perfume/promise.rb, line 60
def self.inherited(child)
  child.const_set(:Warning, Warning)
end
new(*) click to toggle source
Calls superclass method
# File lib/perfume/promise.rb, line 64
def initialize(*)
  super

  @__before     = []
  @__on_success = []
  @__on_failure = []
end

Public Instance Methods

before(&block) click to toggle source
# File lib/perfume/promise.rb, line 72
def before(&block)
  @__before << block
  self
end
call(&block) click to toggle source

Pubic: Safely executes your logic defined in call!, taking care that all callbacks are properly called.

# File lib/perfume/promise.rb, line 103
def call(&block)
  @__ok = false
  @__before.each(&:call)
  success_callbacks = @__on_success + [block]
  call!.tap { |result| success_callbacks.compact.each { |callback| callback.call(result) } }
  @__ok = true
  self
rescue Warning => err
  log.warn(err)
  @__on_failure.each { |callback| callback.call(err) }
  return nil
end
call!() click to toggle source

Public: Your logic goes here. Flow is broken by raising an exception of local Error class or child classes. Any return value will be passed to on-success callbacks.

# File lib/perfume/promise.rb, line 97
def call!
  raise NotImplementedError
end
failure(&block) click to toggle source
# File lib/perfume/promise.rb, line 82
def failure(&block)
  @__on_failure << block
  self
end
failure?() click to toggle source
# File lib/perfume/promise.rb, line 91
def failure?
  !success?
end
success(&block) click to toggle source
# File lib/perfume/promise.rb, line 77
def success(&block)
  @__on_success << block
  self
end
success?() click to toggle source
# File lib/perfume/promise.rb, line 87
def success?
  @__ok
end