diff --git a/README.md b/README.md index 85f3bc2e..53e8a130 100644 --- a/README.md +++ b/README.md @@ -1118,6 +1118,26 @@ Great sadness! => [:passed] ``` +#### DependentDeferrable + +`DependentDeferrable` depends on children deferrables. A `DependentDeferrable` +succeeds only when every child succeeds and fails immediately when any child +fails + +```ruby +> d1 = EM::DefaultDeferrable.new +=> # +> d2 = EM::DefaultDeferrable.new +=> # +> d = EM::DependentDeferrable.on(d1, d2) +=> # +> d.callback {|a, b| puts "a: #{a} b: #{b}"} +=> [#] +> d1.succeed 'one', 'one more' +> d2.succeed :two +a: ["one", "one more"] b: [:two] +``` + #### ThreadAwareDeferrable ```ruby diff --git a/motion/reactor/deferrable.rb b/motion/reactor/deferrable.rb index 63cd15be..346cf19e 100644 --- a/motion/reactor/deferrable.rb +++ b/motion/reactor/deferrable.rb @@ -147,6 +147,14 @@ def timeout(seconds) @deferred_timeout = Timer.new(seconds) {me.fail} end + def deferred_status + @deferred_status ||= :unknown + end + + def deferred_args + @deferred_args + end + end end end diff --git a/motion/reactor/dependent_deferrable.rb b/motion/reactor/dependent_deferrable.rb new file mode 100644 index 00000000..088c9ffb --- /dev/null +++ b/motion/reactor/dependent_deferrable.rb @@ -0,0 +1,27 @@ +module BubbleWrap + module Reactor + class DependentDeferrable < DefaultDeferrable + # args are Deferrable(s) which the returned Deferrable depends on. + # returns a Deferrable that depends on args. + # which: + # succeeds only when every Deferrable in args succeeds + # fails immediately when any Deferrable in args fails + # Have to be careful that #deferred_args for DependentDeferrable is a list of #deferred_args from its children Deferrable(s). + def self.on(*args) + deferrable = self.new + @children_deferrables = args + @children_deferrables.each do |e| + e.callback do |result| + if @children_deferrables.all? {|a| a.deferred_status == :succeeded} + deferrable.succeed(*@children_deferrables.map(&:deferred_args)) + end + end + e.errback do |result| + deferrable.fail(*e.deferred_args) + end + end + deferrable + end + end + end +end diff --git a/spec/motion/reactor/dependent_deferrable_spec.rb b/spec/motion/reactor/dependent_deferrable_spec.rb new file mode 100644 index 00000000..4aa7d9e9 --- /dev/null +++ b/spec/motion/reactor/dependent_deferrable_spec.rb @@ -0,0 +1,93 @@ +describe BubbleWrap::Reactor::DependentDeferrable do + + before do + @subject = Class.new do + include BubbleWrap::Reactor::Deferrable + end + @d1 = @subject.new + @d2 = @subject.new + @object = BubbleWrap::Reactor::DependentDeferrable.on(@d1, @d2) + end + + describe '.callback' do + + it "calls the callback block with arguments after all children .succeed" do + args1 = [1, 2] + args2 = [3, 4] + @object.callback do |*passed_args| + passed_args.should.equal [args1, args2] + end + @d1.succeed *args1 + @d2.succeed *args2 + end + + it "calls the callback block with arguments only after all children .succeed" do + execution_order = [] + @object.callback do |*passed_args| + execution_order << 3 + end + execution_order << 1 + @d1.succeed :succeeds + execution_order << 2 + @d2.succeed :succeeds + execution_order.should.equal [1, 2, 3] + end + + it "calls the callback if even if the deferrable already succeeded" do + @d1.succeed :succeeded1 + @d2.succeed :succeeded2 + @object.callback do |*args| + NSLog "args: #{args} class: #{args.class}" + args[0].should.equal [:succeeded1] + args[1].should.equal [:succeeded2] + end + end + + end + + describe '.errback' do + it "calls the errback block after a child fails" do + args1 = [1, 2] + args2 = [3, 4] + @object.errback do |*passed_args| + passed_args.should.equal args1 + end + @d1.fail *args1 + end + + it "calls the errback block immediately after a child fails" do + execution_order = [] + @object.errback do |*passed_args| + execution_order << 2 + end + execution_order << 1 + @d1.fail :fail + execution_order << 3 + @d2.fail :fail + execution_order.should.equal [1, 2, 3] + end + + it "calls the errback block after a child fails even if others succeeds" do + args1 = [1, 2] + args2 = [3, 4] + callback_called = false + @object.callback do |*passed_args| + callback_called = true + end + @object.errback do |*passed_args| + passed_args.should.equal args2 + end + @d1.succeed *args1 + @d2.fail *args2 + callback_called.should.equal false + end + + it "calls the errback block even if the deferrable failed first" do + @d1.fail :err + @object.errback do |err| + err.should.equal :err + end + end + end + +end