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:
-
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
-
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.
-
Block passed to call is added to on success callbacks:
FindWally.call do |location| puts location end $ "Madrid"
Constants
- Warning
Public Class Methods
# File lib/perfume/promise.rb, line 60 def self.inherited(child) child.const_set(:Warning, Warning) end
# File lib/perfume/promise.rb, line 64 def initialize(*) super @__before = [] @__on_success = [] @__on_failure = [] end
Public Instance Methods
# File lib/perfume/promise.rb, line 72 def before(&block) @__before << block self end
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
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
# File lib/perfume/promise.rb, line 82 def failure(&block) @__on_failure << block self end
# File lib/perfume/promise.rb, line 91 def failure? !success? end
# File lib/perfume/promise.rb, line 77 def success(&block) @__on_success << block self end
# File lib/perfume/promise.rb, line 87 def success? @__ok end