Deferrable Gratification
Purpose
Deferrable Gratification (DG) makes evented code less error-prone and easier to compose, and thus easier to create higher-level abstractions around. It also enhances the API offered by Ruby Deferrables to make them more pleasant to work with.
Documentation
- API
- Behaviour specs (generated from RSpec code examples)
Components
It currently consists of the following components:
DG::Fluent
: fluent (aka chainable) syntax for registering multiple callbacks and errbacks to the same Deferrable.DG::Bothback
: a#bothback
method for registering code to run on either success or failure.DG::Combinators
: a combinator library for building up complex asynchronous operations out of simpler ones.
DG::Fluent
Use JQuery-style fluent syntax for registering several callbacks and errbacks on the same Deferrable. e.g.
DeferrableMonkeyShaver.new(monkey).
callback { puts "Monkey is shaved" }.
callback { monkey.entertain! }.
errback {|e| puts "Unable to shave monkey! #{e}" }.
errback {|_| monkey.terminate! }.
shave
DG::Bothback
Register code to run on either success or failure: shorthand for calling both
#callback
and #errback
with the same code block.
DG::Combinators
Allows building up higher-level asynchronous abstractions by composing simpler asynchronous operations, without having to manually wire callbacks together and remember to propagate errors correctly.
Motivating example: assume we have an asynchronous database API DB.query
which returns a Deferrable to communicate when the query finishes. (See
the API docs for DG::Combinators
for more detail.)
def product_names_for_username(username)
DB.query('SELECT id FROM users WHERE username = ?', username).bind! do |user_id|
DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
end.map do |product_names|
product_names.join(', ')
end
end