Module: DeferrableGratification::Combinators

Defined in:
lib/deferrable_gratification/combinators.rb,
lib/deferrable_gratification/combinators/bind.rb

Overview

Combinators for building up higher-level asynchronous abstractions by composing simpler asynchronous operations, without having to manually wire callbacks together and remember to propagate errors correctly.

Examples:

Perform a sequence of database queries and transform the result.

# With DG::Combinators:
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

status = product_names_for_username('bob')

status.callback {|product_names| ... }
# If both queries complete successfully, the callback receives the string
# "Car, Spoon, Coffee".  The caller doesn't have to know that two separate
# queries were made, or that the query result needed transforming into the
# desired format: he just gets the event he cares about.

status.errback {|error| puts "Oh no!  #{error}" }
# If either query went wrong, the errback receives the error that occurred.

# Without DG::Combinators:
def product_names_for_username(username)
  product_names_status = EM::DefaultDeferrable.new
  query1_status = DB.query('SELECT id FROM users WHERE username = ?', username)
  query1_status.callback do |user_id|
    query2_status = DB.query('SELECT name FROM products WHERE user_id = ?', user_id)
    query2_status.callback do |product_names|
      product_names = product_names.join(', ')
      # N.B. don't forget to report success to the caller!
      product_names_status.succeed(product_names)
    end
    query2_status.errback do |error|
      # N.B. don't forget to tell the caller we failed!
      product_names_status.fail(error)
    end
  end
  query1_status.errback do |error|
    # N.B. don't forget to tell the caller we failed!
    product_names_status.fail(error)
  end
  # oh yes, and don't forget to return this!
  product_names_status
end

Defined Under Namespace

Modules: ClassMethods Classes: Bind

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.included(base) ⇒ Object

Boilerplate hook to extend ClassMethods.



147
148
149
# File 'lib/deferrable_gratification/combinators.rb', line 147

def self.included(base)
  base.send :extend, ClassMethods
end

Instance Method Details

#>>(prok) ⇒ Deferrable

Alias for #bind!.

Note that this takes a Proc (e.g. a lambda) while #bind! takes a block.

Examples:

Perform a database query that depends on the result of a previous query.

DB.query('first query') >> lambda {|result| DB.query("query with #{result}") }

Parameters:

  • prok (Proc)

    proc to call with the successful result of self. Assumed to return a Deferrable representing the status of its own operation.

Returns:

  • (Deferrable)

    status of the compound operation of passing the result of self into the proc.



71
72
73
# File 'lib/deferrable_gratification/combinators.rb', line 71

def >>(prok)
  Bind.setup!(self, &prok)
end

#bind!(&block) ⇒ Deferrable

Register callbacks so that when this Deferrable succeeds, its result will be passed to the block, which is assumed to return another Deferrable representing the status of a second operation.

If this operation fails, the block will not be run. If either operation fails, the compound Deferrable returned will fire its errbacks, meaning callers don’t have to know about the inner operations and can just subscribe to the result of #bind!.

If you find yourself writing lots of nested #bind! calls, you can equivalently rewrite them as a chain and remove the nesting: e.g.

a.bind! do |x|
  b(x).bind! do |y|
    c(y).bind! do |z|
      d(z)
    end
  end
end

has the same behaviour as

a.bind! do |x|
  b(x)
end.bind! do |y|
  c(y)
end.bind! do |z|
  d(y)
end

As well as being more readable due to avoiding left margin inflation, this prevents introducing bugs due to inadvertent local variable capture by the nested blocks.

Examples:

Perform a web request based on the result of a database query.

DB.query('url').bind! {|url| HTTP.get(url) }.
  callback {|response| puts "Got response!" }

Parameters:

  • &block

    block to call with the successful result of self. Assumed to return a Deferrable representing the status of its own operation.

Returns:

  • (Deferrable)

    status of the compound operation of passing the result of self into the block.

See Also:



123
124
125
# File 'lib/deferrable_gratification/combinators.rb', line 123

def bind!(&block)
  Bind.setup!(self, &block)
end

#map(&block) ⇒ Deferrable

Transform the result of this Deferrable by invoking block, returning a Deferrable which succeeds with the transformed result.

If this operation fails, the operation will not be run, and the returned Deferrable will also fail.

operation in some way.

Examples:

Retrieve a web page and call back with its title.

HTTP.request(url).map {|page| Hpricot(page).at(:title).inner_html }

Parameters:

  • &block

    block that transforms the expected result of this

Returns:

  • (Deferrable)

    Deferrable that will succeed if this operation did, after transforming its result.



141
142
143
# File 'lib/deferrable_gratification/combinators.rb', line 141

def map(&block)
  bind!(&block)
end