Skip to content

Commit

Permalink
Add AllSuccesses join combinator
Browse files Browse the repository at this point in the history
This is modeled on the Successes combinator, but has
the semantics that either all operations succeed or it
fails with the first failure.
  • Loading branch information
Tim Perkins committed Dec 8, 2014
1 parent 96ef6b3 commit 1c66f55
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# deferrable_gratification changes

## v1.1.0
- Add `DG.all_successes` to asynchronously perform a set of operations, and
either succeed when all operations succeed, or fail with the first failure.

## v1.0.0
- Remove the `Fluent` module as the same syntax is available in
`EventMachine` 1.0.3.
Expand Down
22 changes: 22 additions & 0 deletions lib/deferrable_gratification/combinators.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,28 @@ def join_successes(*operations)
Join::Successes.setup!(*operations)
end

# Combinator that waits for the supplied asynchronous operations
# to succeed or fail, then succeeds with the results of all those
# operations that were successful.
#
# This Deferrable will fail if any of the operations fail. It will either
# succeed with all the operations or fail with the first failure.
#
# The successful results are guaranteed to be in the same order as the
# operations were passed in (which may _not_ be the same as the
# chronological order in which they succeeded).
#
# @param [*Deferrable] *operations deferred statuses of asynchronous
# operations to wait for.
#
# @return [Deferrable] a deferred status that will either succeed after
# all the +operations+ have succeeded or fail after the first failed
# operation; its callbacks will be passed an +Enumerable+ containing
# the results of those operations that succeeded.
def all_successes(*operations)
Join::AllSuccesses.setup!(*operations)
end

# Combinator that waits for any of the supplied asynchronous operations
# to succeed, and succeeds with the result of the first (chronologically)
# to do so.
Expand Down
27 changes: 27 additions & 0 deletions lib/deferrable_gratification/combinators/join.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,33 @@ def finish
end
end

# Combinator that waits for the supplied asynchronous operations
# to succeed or fail, then succeeds with the results of all those
# operations that were successful.
#
# This Deferrable will fail if any of the operations fail. It will either
# succeed with all the operations or fail with the first failure.
#
# The successful results are guaranteed to be in the same order as the
# operations were passed in (which may _not_ be the same as the
# chronological order in which they succeeded).
#
# You probably want to call {ClassMethods#all_successes} rather than
# using this class directly.
class AllSuccesses < Join
private
def done?
failures.length > 0 || all_completed?
end

def finish
if failures.length > 0
fail(failures.first)
else
succeed(successes)
end
end
end

# Combinator that waits for any of the supplied asynchronous operations
# to succeed, and succeeds with the result of the first (chronologically)
Expand Down
2 changes: 1 addition & 1 deletion lib/deferrable_gratification/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module DeferrableGratification
VERSION = '1.0.0'
VERSION = '1.1.0'
end
61 changes: 61 additions & 0 deletions spec/deferrable_gratification/combinators_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,67 @@ def bind!() first_query.bind! {|id| raise "id #{id} not authorised" } end
end
end

describe '.all_successes' do
describe 'DG.all_successes()' do
subject { DG.all_successes() }
it { should succeed_with [] }
end

describe 'DG.all_successes(first, second)' do
let(:first) { EM::DefaultDeferrable.new }
let(:second) { EM::DefaultDeferrable.new }
subject { DG.all_successes(first, second) }

it 'should not succeed or fail' do
subject.should_not succeed_with_anything
subject.should_not fail_with_anything
end

describe 'after first succeeds with :one' do
before { first.succeed :one }

it 'should not succeed or fail' do
subject.should_not succeed_with_anything
subject.should_not fail_with_anything
end

describe 'after second succeeds with :two' do
before { second.succeed :two }

it { should succeed_with [:one, :two] }
end

describe 'after second fails' do
before { second.fail RuntimeError.new('oops') }

it { should fail_with('oops') }
end
end

describe 'after both fail' do
before do
first.fail RuntimeError.new('oops 1')
second.fail RuntimeError.new('oops 2')
end

it { should fail_with('oops 1') }
end

describe 'preserving order of operations' do
describe 'if second succeeds before first does' do
subject do
DG.all_successes(first, second).tap do |successes|
second.succeed :two
first.succeed :one
end
end
it 'should still succeed with [:one, :two]' do
subject.should succeed_with [:one, :two]
end
end
end
end
end

describe '.join_first_success' do
describe 'DG.join_first_success()' do
Expand Down

0 comments on commit 1c66f55

Please sign in to comment.