Skip to content

Commit

Permalink
Merge pull request #4 from Yesware/all-successes
Browse files Browse the repository at this point in the history
Add AllSuccesses join combinator
  • Loading branch information
Tim Perkins committed Dec 8, 2014
2 parents 96ef6b3 + 1c66f55 commit ad7e277
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 ad7e277

Please sign in to comment.