ione 1.1.5 → 1.2.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: db1f327275f2a707826e35fe337e9244e24579d3
4
- data.tar.gz: 49634515f2915a2d148fc9bd7261d3995a9c9624
3
+ metadata.gz: b1ae6f32b5b3e404b07f6ec0a0b20856a0458719
4
+ data.tar.gz: 6a1e98dcd2cb657b2cbbe7dcc29514e38c672608
5
5
  SHA512:
6
- metadata.gz: 5573efe1dab6a07649012f63b02dd160023033ba674cb582050d57aaeef4a98cbd00d43651d6d732c3e2d98e32b2cdf838a678836904b9066cf4bb9fda22d806
7
- data.tar.gz: 8a0b37f8dde7c69f1f05a4bed764c2c3cb5acdd2460fed882d71dd4bc0c1155983e07a2cf2c2ee76510eff1afd8b4f8496a310b308c7838bf411df4a99b0d4ac
6
+ metadata.gz: b57e6559e0be8dca588b6f4b7d1cf069041848d368de377ad0c4db94f4863c0d0194d66843190fc18f1de2c92a0465429d32773d4994c8aee405a584aa167544
7
+ data.tar.gz: 8a30c9516df0d52376ebda8958446fdb35d4fb9adbe4028d180afb438f31061bb77ede9ecebde1e7a640ba19364a2a2a35acc691288585de35e2ea64453d5526
data/lib/ione/future.rb CHANGED
@@ -44,8 +44,13 @@ module Ione
44
44
  #
45
45
  # @param [Ione::Future] future the future to observe
46
46
  def observe(future)
47
- future.on_value { |v| fulfill(v) }
48
- future.on_failure { |e| fail(e) }
47
+ future.on_complete do |_, v, e|
48
+ if e
49
+ fail(e)
50
+ else
51
+ fulfill(v)
52
+ end
53
+ end
49
54
  end
50
55
 
51
56
  # Run the given block and fulfill this promise with its result. If the block
@@ -81,12 +86,14 @@ module Ione
81
86
  # The value of the combined future is an array of the values of the
82
87
  # constituent futures.
83
88
  #
84
- # @param [Array<Ione::Future>] futures the futures to combine
89
+ # @param [Array<Ione::Future>] futures the futures to combine (this argument
90
+ # can be a splatted array or a regular array passed as sole argument)
85
91
  # @return [Ione::Future<Array>] an array of the values of the constituent
86
92
  # futures
87
93
  def all(*futures)
88
94
  return resolved([]) if futures.empty?
89
- return futures.first.map { |v| [v] } if futures.size == 1
95
+ futures = futures.size == 1 && futures.first.is_a?(Enumerable) ? futures.first : futures
96
+ return futures.first.map { |v| [v] } if futures.is_a?(Array) && futures.size == 1
90
97
  CombinedFuture.new(futures)
91
98
  end
92
99
 
@@ -94,14 +101,90 @@ module Ione
94
101
  # (resolved) of the specified futures. If all of the futures fail, the
95
102
  # returned future will also fail (with the error of the last failed future).
96
103
  #
97
- # @param [Array<Ione::Future>] futures the futures to monitor
104
+ # @param [Array<Ione::Future>] futures the futures to monitor (this argument
105
+ # can be a splatted array or a regular array passed as sole argument)
98
106
  # @return [Ione::Future] a future which represents the first completing future
99
107
  def first(*futures)
100
108
  return resolved if futures.empty?
101
- return futures.first if futures.size == 1
109
+ futures = futures.size == 1 && futures.first.is_a?(Enumerable) ? futures.first : futures
110
+ return futures.first if futures.is_a?(Array) && futures.size == 1
102
111
  FirstFuture.new(futures)
103
112
  end
104
113
 
114
+ # Takes calls the block once for each element in an array, expecting each
115
+ # invocation to return a future, and returns a future that resolves to
116
+ # an array of the values of those futures.
117
+ #
118
+ # @example
119
+ # ids = [1, 2, 3]
120
+ # future = Future.traverse(ids) { |id| load_thing(id) }
121
+ # future.value # => [thing1, thing2, thing3]
122
+ #
123
+ # @param [Array<Object>] values an array whose elements will be passed to
124
+ # the block, one by one
125
+ # @yieldparam [Object] value each element from the array
126
+ # @yieldreturn [Ione::Future] a future
127
+ # @return [Ione::Future] a future that will resolve to an array of the values
128
+ # of the futures returned by the block
129
+ def traverse(values, &block)
130
+ all(*values.map(&block))
131
+ rescue => e
132
+ failed(e)
133
+ end
134
+
135
+ # Returns a future that will resolve to a value which is the reduction of
136
+ # the values of a list of source futures.
137
+ #
138
+ # This is essentially a parallel, streaming version of {Enumerable#reduce},
139
+ # but for futures. Use this method for example when you want to do a number
140
+ # of asynchronous operations in parallel and then merge the results together
141
+ # when all are done.
142
+ #
143
+ # The block will not be called concurrently, which means that unless you're
144
+ # handling the initial value or other values in the scope of the block you
145
+ # don't need (and shouldn't) do any locking to ensure that the accumulator
146
+ # passed to the block is safe to modify. It is, of course, even better if
147
+ # you don't modify the accumulator, but return a new, immutable value on
148
+ # each invocation.
149
+ #
150
+ # @example Merging the results of multipe asynchronous calls
151
+ # futures = ... # a list of futures that will resolve to hashes
152
+ # merged_future = Future.reduce(futures, {}) do |accumulator, value|
153
+ # accumulator.merge(value)
154
+ # end
155
+ #
156
+ # @example Reducing with an associative and commutative function, like addition
157
+ # futures = ... # a list of futures that will resolve to numbers
158
+ # sum_future = Future.reduce(futures, 0, ordered: false) do |accumulator, value|
159
+ # accumulator + value
160
+ # end
161
+ #
162
+ # @param [Array<Ione::Future>] futures an array of futures whose values
163
+ # should be reduced
164
+ # @param [Object] initial_value the initial value of the accumulator
165
+ # @param [Hash] options
166
+ # @option options [Boolean] :ordered (true) whether or not to respect the
167
+ # order of the input when reducing – when true the block will be called
168
+ # with the values of the source futures in the order they have in the
169
+ # given list, when false the block will be called in the order that the
170
+ # futures resolve (which means that your reducer function needs to be
171
+ # associative and commutative).
172
+ # @yieldparam [Object] accumulator the value of the last invocation of the
173
+ # block, or the initial value if this is the first invocation
174
+ # @yieldparam [Object] value the value of one of the source futures
175
+ # @yieldreturn [Object] the value to pass as accumulator to the next
176
+ # invocation of the block
177
+ # @return [Ione::Future] a future that will resolve to the value returned
178
+ # from the last invocation of the block, or nil when the list of futures
179
+ # is empty.
180
+ def reduce(futures, initial_value=nil, options=nil, &reducer)
181
+ if options && options[:ordered] == false
182
+ UnorderedReducingFuture.new(futures, initial_value, reducer)
183
+ else
184
+ OrderedReducingFuture.new(futures, initial_value, reducer)
185
+ end
186
+ end
187
+
105
188
  # Creates a new pre-resolved future.
106
189
  #
107
190
  # @param [Object, nil] value the value of the created future
@@ -131,10 +214,19 @@ module Ione
131
214
  # @yieldreturn [Object] the transformed value
132
215
  # @return [Ione::Future] a new future representing the transformed value
133
216
  def map(value=nil, &block)
134
- CompletableFuture.new.tap do |f|
135
- on_failure { |e| f.fail(e) }
136
- on_value { |v| run(f, value, block, v) }
217
+ f = CompletableFuture.new
218
+ on_complete do |_, v, e|
219
+ if e
220
+ f.fail(e)
221
+ else
222
+ begin
223
+ f.resolve(block ? block.call(v) : value)
224
+ rescue => e
225
+ f.fail(e)
226
+ end
227
+ end
137
228
  end
229
+ f
138
230
  end
139
231
 
140
232
  # Returns a new future representing a transformation of this future's value,
@@ -149,16 +241,82 @@ module Ione
149
241
  # @yieldreturn [Ione::Future] a future representing the transformed value
150
242
  # @return [Ione::Future] a new future representing the transformed value
151
243
  def flat_map(&block)
152
- CompletableFuture.new.tap do |f|
153
- on_failure { |e| f.fail(e) }
154
- on_value { |v| chain(f, block, v) }
244
+ f = CompletableFuture.new
245
+ on_complete do |_, v, e|
246
+ if e
247
+ f.fail(e)
248
+ else
249
+ begin
250
+ ff = block.call(v)
251
+ ff.on_complete do |_, vv, ee|
252
+ if ee
253
+ f.fail(ee)
254
+ else
255
+ f.resolve(vv)
256
+ end
257
+ end
258
+ rescue => e
259
+ f.fail(e)
260
+ end
261
+ end
155
262
  end
263
+ f
264
+ end
265
+
266
+ # Returns a new future representing a transformation of this future's value,
267
+ # similarily to {#map}, but acts as {#flat_map} when the block returns a
268
+ # {Future}.
269
+ #
270
+ # This method is useful when you want to transform the value of a future,
271
+ # but whether or not it can be done synchronously or require an asynchronous
272
+ # operation depends on the value of the future.
273
+ #
274
+ # @example
275
+ # future1 = load_something
276
+ # future2 = future1.then do |result|
277
+ # if result.empty?
278
+ # # make a new async call to load fallback value
279
+ # load_something_else
280
+ # else
281
+ # result
282
+ # end
283
+ # end
284
+ #
285
+ # @yieldparam [Object] value the value of this future
286
+ # @yieldreturn [Object, Ione::Future] the transformed value, or a future
287
+ # that will resolve to the transformed value.
288
+ # @return [Ione::Future] a new future representing the transformed value
289
+ def then(&block)
290
+ f = CompletableFuture.new
291
+ on_complete do |_, v, e|
292
+ if e
293
+ f.fail(e)
294
+ else
295
+ begin
296
+ fv = block.call(v)
297
+ if fv.respond_to?(:on_complete)
298
+ fv.on_complete do |_, vv, ee|
299
+ if ee
300
+ f.fail(ee)
301
+ else
302
+ f.resolve(vv)
303
+ end
304
+ end
305
+ else
306
+ f.resolve(fv)
307
+ end
308
+ rescue => e
309
+ f.fail(e)
310
+ end
311
+ end
312
+ end
313
+ f
156
314
  end
157
315
 
158
316
  # Returns a new future which represents either the value of the original
159
317
  # future, or the result of the given block, if the original future fails.
160
318
  #
161
- # This method is similar to{#map}, but is triggered by a failure. You can
319
+ # This method is similar to {#map}, but is triggered by a failure. You can
162
320
  # also think of it as a `rescue` block for asynchronous operations.
163
321
  #
164
322
  # If the block raises an error a failed future with that error will be
@@ -175,10 +333,19 @@ module Ione
175
333
  # @yieldreturn [Object] the value of the new future
176
334
  # @return [Ione::Future] a new future representing a value recovered from the error
177
335
  def recover(value=nil, &block)
178
- CompletableFuture.new.tap do |f|
179
- on_failure { |e| run(f, value, block, e) }
180
- on_value { |v| f.resolve(v) }
336
+ f = CompletableFuture.new
337
+ on_complete do |_, v, e|
338
+ if e
339
+ begin
340
+ f.resolve(block ? block.call(e) : value)
341
+ rescue => e
342
+ f.fail(e)
343
+ end
344
+ else
345
+ f.resolve(v)
346
+ end
181
347
  end
348
+ f
182
349
  end
183
350
 
184
351
  # Returns a new future which represents either the value of the original
@@ -204,27 +371,26 @@ module Ione
204
371
  # @return [Ione::Future] a new future representing a value recovered from the
205
372
  # error
206
373
  def fallback(&block)
207
- CompletableFuture.new.tap do |f|
208
- on_failure { |e| chain(f, block, e) }
209
- on_value { |v| f.resolve(v) }
374
+ f = CompletableFuture.new
375
+ on_complete do |_, v, e|
376
+ if e
377
+ begin
378
+ ff = block.call(e)
379
+ ff.on_complete do |_, vv, ee|
380
+ if ee
381
+ f.fail(ee)
382
+ else
383
+ f.resolve(vv)
384
+ end
385
+ end
386
+ rescue => e
387
+ f.fail(e)
388
+ end
389
+ else
390
+ f.resolve(v)
391
+ end
210
392
  end
211
- end
212
-
213
- private
214
-
215
- def run(f, value, producer, arg)
216
- value = producer ? producer.call(arg) : value
217
- f.resolve(value)
218
- rescue => e
219
- f.fail(e)
220
- end
221
-
222
- def chain(f, constructor, arg)
223
- ff = constructor.call(arg)
224
- ff.on_failure { |e| f.fail(e) }
225
- ff.on_value { |v| f.resolve(v) }
226
- rescue => e
227
- f.fail(e)
393
+ f
228
394
  end
229
395
  end
230
396
 
@@ -251,7 +417,7 @@ module Ione
251
417
  end
252
418
  end
253
419
  if run_immediately
254
- listener.call(self) rescue nil
420
+ listener.call(self, @value, @error) rescue nil
255
421
  end
256
422
  nil
257
423
  end
@@ -417,7 +583,7 @@ module Ione
417
583
  listener.call(v) rescue nil
418
584
  end
419
585
  complete_listeners.each do |listener|
420
- listener.call(self) rescue nil
586
+ listener.call(self, v, nil) rescue nil
421
587
  end
422
588
  nil
423
589
  end
@@ -442,7 +608,7 @@ module Ione
442
608
  listener.call(error) rescue nil
443
609
  end
444
610
  complete_listeners.each do |listener|
445
- listener.call(self) rescue nil
611
+ listener.call(self, nil, error) rescue nil
446
612
  end
447
613
  nil
448
614
  end
@@ -452,40 +618,135 @@ module Ione
452
618
  class CombinedFuture < CompletableFuture
453
619
  def initialize(futures)
454
620
  super()
455
- values = Array.new(futures.size)
456
- remaining = futures.size
621
+ remaining = futures.count
622
+ values = Array.new(remaining)
457
623
  futures.each_with_index do |f, i|
458
- f.on_value do |v|
459
- @lock.lock
460
- begin
461
- values[i] = v
462
- remaining -= 1
463
- ensure
464
- @lock.unlock
624
+ f.on_complete do |_, v, e|
625
+ unless failed?
626
+ if e
627
+ fail(e)
628
+ else
629
+ @lock.lock
630
+ begin
631
+ values[i] = v
632
+ remaining -= 1
633
+ ensure
634
+ @lock.unlock
635
+ end
636
+ if remaining == 0
637
+ resolve(values)
638
+ end
639
+ end
465
640
  end
466
- if remaining == 0
467
- resolve(values)
641
+ end
642
+ end
643
+ end
644
+ end
645
+
646
+ # @private
647
+ class ReducingFuture < CompletableFuture
648
+ def initialize(futures, initial_value, reducer)
649
+ super()
650
+ @futures = Array(futures)
651
+ @remaining = @futures.size
652
+ @initial_value = initial_value
653
+ @accumulator = initial_value
654
+ @reducer = reducer
655
+ end
656
+
657
+ private
658
+
659
+ def reduce_one(value)
660
+ unless failed?
661
+ @lock.lock
662
+ begin
663
+ if @accumulator
664
+ @accumulator = @reducer.call(@accumulator, value)
665
+ else
666
+ @accumulator = value
468
667
  end
668
+ @remaining -= 1
669
+ rescue => e
670
+ @lock.unlock
671
+ fail(e)
672
+ else
673
+ @lock.unlock
469
674
  end
470
- f.on_failure do |e|
471
- unless failed?
675
+ unless failed?
676
+ if @remaining == 0
677
+ resolve(@accumulator)
678
+ :done
679
+ else
680
+ :continue
681
+ end
682
+ end
683
+ end
684
+ end
685
+ end
686
+
687
+ # @private
688
+ class OrderedReducingFuture < ReducingFuture
689
+ def initialize(futures, initial_value, reducer)
690
+ super
691
+ if @remaining > 0
692
+ reduce_next(0)
693
+ else
694
+ resolve(@initial_value)
695
+ end
696
+ end
697
+
698
+ private
699
+
700
+ def reduce_next(i)
701
+ @futures[i].on_complete do |_, v, e|
702
+ unless failed?
703
+ if e
472
704
  fail(e)
705
+ elsif reduce_one(v) == :continue
706
+ reduce_next(i + 1)
473
707
  end
474
708
  end
475
709
  end
476
710
  end
477
711
  end
478
712
 
713
+ # @private
714
+ class UnorderedReducingFuture < ReducingFuture
715
+ def initialize(futures, initial_value, reducer)
716
+ super
717
+ if @remaining > 0
718
+ futures.each do |f|
719
+ f.on_complete do |_, v, e|
720
+ unless failed?
721
+ if e
722
+ fail(e)
723
+ else
724
+ reduce_one(v)
725
+ end
726
+ end
727
+ end
728
+ end
729
+ else
730
+ resolve(@initial_value)
731
+ end
732
+ end
733
+ end
734
+
479
735
  # @private
480
736
  class FirstFuture < CompletableFuture
481
737
  def initialize(futures)
482
738
  super()
483
739
  futures.each do |f|
484
- f.on_value do |value|
485
- resolve(value) unless completed?
486
- end
487
- f.on_failure do |e|
488
- fail(e) if futures.all?(&:failed?)
740
+ f.on_complete do |_, v, e|
741
+ unless completed?
742
+ if e
743
+ if futures.all?(&:failed?)
744
+ fail(e)
745
+ end
746
+ else
747
+ resolve(v)
748
+ end
749
+ end
489
750
  end
490
751
  end
491
752
  end
@@ -516,7 +777,7 @@ module Ione
516
777
  end
517
778
 
518
779
  def on_complete(&listener)
519
- listener.call(self) rescue nil
780
+ listener.call(self, @value, nil) rescue nil
520
781
  end
521
782
 
522
783
  def on_value(&listener)
@@ -554,7 +815,7 @@ module Ione
554
815
  end
555
816
 
556
817
  def on_complete(&listener)
557
- listener.call(self) rescue nil
818
+ listener.call(self, nil, @error) rescue nil
558
819
  end
559
820
 
560
821
  def on_value
@@ -9,7 +9,6 @@ module Ione
9
9
  @host = host
10
10
  @port = port
11
11
  @state = :connecting
12
- @lock = Mutex.new
13
12
  @closed_promise = Promise.new
14
13
  end
15
14
 
@@ -17,10 +16,7 @@ module Ione
17
16
  #
18
17
  # @return [true, false] returns false if the connection was already closed
19
18
  def close(cause=nil)
20
- @lock.synchronize do
21
- return false if @state == :closed
22
- @state = :closed
23
- end
19
+ return false if @state == :closed
24
20
  if @io
25
21
  begin
26
22
  @io.close
@@ -29,6 +25,7 @@ module Ione
29
25
  # nothing to do, the socket was most likely already closed
30
26
  end
31
27
  end
28
+ @state = :closed
32
29
  if cause
33
30
  @closed_promise.fail(cause)
34
31
  else
@@ -14,6 +14,7 @@ module Ione
14
14
  @unblocker = unblocker
15
15
  @clock = clock
16
16
  @socket_impl = socket_impl
17
+ @lock = Mutex.new
17
18
  @write_buffer = ByteBuffer.new
18
19
  @connected_promise = Promise.new
19
20
  on_closed(&method(:cleanup_on_close))
@@ -293,21 +293,13 @@ module Ione
293
293
  end
294
294
 
295
295
  def cancel_timer(timer_future)
296
- timer = nil
297
296
  @lock.lock
298
- begin
299
- if (timer_pair = @timers.find { |pair| (p = pair[1]) && p.future == timer_future })
300
- @timers = @timers.reject { |pair| pair[1].nil? || pair == timer_pair }
301
- timer = timer_pair[1]
302
- timer_pair[1] = nil
303
- end
304
- ensure
305
- @lock.unlock
306
- end
307
- if timer
308
- timer.fail(CancelledError.new)
309
- end
297
+ timer_pair = @timers.find { |pair| (p = pair[1]) && p.future == timer_future }
298
+ @timers = @timers.reject { |pair| pair[1].nil? || pair == timer_pair }
299
+ timer_pair && timer_pair[1].fail(CancelledError.new)
310
300
  nil
301
+ ensure
302
+ @lock.unlock
311
303
  end
312
304
 
313
305
  def close_sockets
@@ -349,25 +341,21 @@ module Ione
349
341
  writables << s if s.connecting? || s.writable?
350
342
  connecting << s if s.connecting?
351
343
  end
352
- r, w, _ = @selector.select(readables, writables, nil, timeout)
353
- connecting.each(&:connect)
354
- r && r.each(&:read)
355
- w && w.each(&:flush)
344
+ begin
345
+ r, w, _ = @selector.select(readables, writables, nil, timeout)
346
+ connecting.each(&:connect)
347
+ r && r.each(&:read)
348
+ w && w.each(&:flush)
349
+ rescue IOError, Errno::EBADF
350
+ end
356
351
  end
357
352
 
358
353
  def check_timers!
359
- now = @clock.now
360
- @timers.each do |pair|
361
- if pair[1] && pair[0] <= now
362
- timer = nil
363
- @lock.lock
364
- begin
365
- timer = pair[1]
366
- pair[1] = nil
367
- ensure
368
- @lock.unlock
369
- end
370
- timer.fulfill
354
+ timers = @timers
355
+ timers.each do |pair|
356
+ if pair[1] && pair[0] <= @clock.now
357
+ pair[1].fulfill
358
+ pair[1] = nil
371
359
  end
372
360
  end
373
361
  end
@@ -8,6 +8,7 @@ module Ione
8
8
  super(host, port)
9
9
  @io = socket
10
10
  @unblocker = unblocker
11
+ @lock = Mutex.new
11
12
  @write_buffer = ByteBuffer.new
12
13
  @state = :connected
13
14
  end
data/lib/ione/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Ione
4
- VERSION = '1.1.5'.freeze
4
+ VERSION = '1.2.0.pre0'.freeze
5
5
  end
@@ -194,21 +194,48 @@ module Ione
194
194
  describe '#on_complete' do
195
195
  context 'registers listeners and' do
196
196
  it 'notifies all listeners when the promise is fulfilled' do
197
+ c1, c2 = false, false
198
+ future.on_complete { c1 = true }
199
+ future.on_complete { c2 = true }
200
+ promise.fulfill('bar')
201
+ c1.should be_true
202
+ c2.should be_true
203
+ end
204
+
205
+ it 'passes the future as the first parameter to the block' do
206
+ f1, f2 = nil, nil
207
+ future.on_complete { |f| f1 = f }
208
+ future.on_complete { |f| f2 = f }
209
+ promise.fulfill('bar')
210
+ f1.should equal(future)
211
+ f2.should equal(future)
212
+ end
213
+
214
+ it 'passes the value as the second parameter to the block' do
197
215
  v1, v2 = nil, nil
198
- future.on_complete { |f| v1 = f.value }
199
- future.on_complete { |f| v2 = f.value }
216
+ future.on_complete { |_, v| v1 = v }
217
+ future.on_complete { |_, v| v2 = v }
200
218
  promise.fulfill('bar')
201
219
  v1.should == 'bar'
202
220
  v2.should == 'bar'
203
221
  end
204
222
 
205
223
  it 'notifies all listeners when the promise fails' do
224
+ c1, c2 = nil, nil
225
+ future.on_complete { c1 = true }
226
+ future.on_complete { c2 = true }
227
+ future.fail(error)
228
+ c1.should be_true
229
+ c2.should be_true
230
+ end
231
+
232
+ it 'passes the error as the third parameter' do
206
233
  e1, e2 = nil, nil
207
- future.on_complete { |f| begin; f.value; rescue => err; e1 = err; end }
208
- future.on_complete { |f| begin; f.value; rescue => err; e2 = err; end }
234
+ future.on_complete { |_, _, e| e1 = e }
235
+ future.on_complete { |_, _, e| e2 = e }
209
236
  future.fail(error)
210
- e1.message.should == error.message
211
- e2.message.should == error.message
237
+ e1.should equal(error)
238
+ e2.should equal(error)
212
239
  end
213
240
 
214
241
  it 'notifies all listeners when the promise is fulfilled, even when one raises an error' do
@@ -228,8 +255,21 @@ module Ione
228
255
  end
229
256
 
230
257
  it 'notifies listeners registered after the promise was fulfilled' do
258
+ f, v, e = nil, nil, nil
231
259
  promise.fulfill('bar')
232
- expect { future.on_complete { |v| raise 'blurgh' } }.to_not raise_error
260
+ future.on_complete { |ff, vv, ee| f = ff; v = vv; e = ee }
261
+ f.should equal(future)
262
+ v.should == 'bar'
263
+ e.should be_nil
264
+ end
265
+
266
+ it 'notifies listeners registered after the promise failed' do
267
+ f, v, e = nil, nil, nil
268
+ promise.fail(StandardError.new('bork'))
269
+ future.on_complete { |ff, vv, ee| f = ff; v = vv; e = ee }
270
+ f.should equal(future)
271
+ v.should be_nil
272
+ e.message.should == 'bork'
233
273
  end
234
274
 
235
275
  it 'notifies listeners registered after the promise failed' do
@@ -366,7 +406,7 @@ module Ione
366
406
  future.value.should == 'bar'
367
407
  end
368
408
 
369
- it 'blocks on #value until fulfilled, when value is nil' do
409
+ it 'blocks on #value until completed, when value is nil' do
370
410
  d = delayed(promise) do |p|
371
411
  p.fulfill
372
412
  end
@@ -382,7 +422,7 @@ module Ione
382
422
  expect { future.value }.to raise_error('bork')
383
423
  end
384
424
 
385
- it 'allows multiple threads to block on #value until fulfilled' do
425
+ it 'allows multiple threads to block on #value until completed' do
386
426
  listeners = Array.new(10) do
387
427
  async(future) do |f|
388
428
  f.value
@@ -396,7 +436,7 @@ module Ione
396
436
 
397
437
  describe '#map' do
398
438
  context 'returns a new future that' do
399
- it 'will be fulfilled with the result of the given block' do
439
+ it 'will be resolved with the result of the given block' do
400
440
  mapped_value = nil
401
441
  p = Promise.new
402
442
  f = p.future.map { |v| v * 2 }
@@ -405,7 +445,7 @@ module Ione
405
445
  mapped_value.should == 3 * 2
406
446
  end
407
447
 
408
- it 'will be fulfilled with the specified value' do
448
+ it 'will be resolved with the specified value' do
409
449
  mapped_value = nil
410
450
  p = Promise.new
411
451
  f = p.future.map(7)
@@ -414,7 +454,7 @@ module Ione
414
454
  mapped_value.should == 7
415
455
  end
416
456
 
417
- it 'will be fulfilled with the result of the given block, even if a value is specified' do
457
+ it 'will be resolved with the result of the given block, even if a value is specified' do
418
458
  mapped_value = nil
419
459
  p = Promise.new
420
460
  f = p.future.map(7) { |v| v * 2 }
@@ -423,7 +463,7 @@ module Ione
423
463
  mapped_value.should == 3 * 2
424
464
  end
425
465
 
426
- it 'will be fulfilled with nil when neither value nor block is specified' do
466
+ it 'will be resolved with nil when neither value nor block is specified' do
427
467
  mapped_value = 3
428
468
  p = Promise.new
429
469
  f = p.future.map
@@ -454,45 +494,97 @@ module Ione
454
494
  end
455
495
 
456
496
  describe '#flat_map' do
457
- it 'works like #map, but expects that the block returns a future' do
497
+ context 'returns a future that' do
498
+ it 'passes the value of the source future to the block, and resolves to the value of the future returned by the block' do
499
+ p = Promise.new
500
+ f = p.future.flat_map { |v| Future.resolved(v * 2) }
501
+ p.fulfill(3)
502
+ f.value.should == 3 * 2
503
+ end
504
+
505
+ it 'fails when the block raises an error' do
506
+ p = Promise.new
507
+ f = p.future.flat_map { |v| raise 'Hurgh' }
508
+ p.fulfill(3)
509
+ expect { f.value }.to raise_error('Hurgh')
510
+ end
511
+ end
512
+
513
+ it 'accepts anything that implements #on_complete as a chained future' do
514
+ fake_future = double(:fake_future)
515
+ fake_future.stub(:on_complete) { |&listener| listener.call(nil, :foobar) }
458
516
  p = Promise.new
459
- f = p.future.flat_map { |v| Future.resolved(v * 2) }
460
- p.fulfill(3)
461
- f.value.should == 3 * 2
517
+ f = p.future.flat_map { fake_future }
518
+ p.fulfill
519
+ f.value.should == :foobar
520
+ end
521
+ end
522
+
523
+ describe '#then' do
524
+ context 'when the block returns a future' do
525
+ it 'works like #flat_map' do
526
+ p = Promise.new
527
+ f = p.future.then { |v| Future.resolved(v * 2) }
528
+ p.fulfill(3)
529
+ f.value.should == 3 * 2
530
+ end
531
+ end
532
+
533
+ context 'when the block returns something that quacks like a future' do
534
+ it 'works like #flat_map' do
535
+ fake_future = double(:fake_future)
536
+ fake_future.stub(:on_complete) { |&listener| listener.call(nil, :foobar) }
537
+ p = Promise.new
538
+ f = p.future.then { |v| fake_future }
539
+ p.fulfill
540
+ f.value.should == :foobar
541
+ end
462
542
  end
463
543
 
464
- it 'fails when the block raises an error' do
544
+ context 'when the block returns something that does not quack like a future' do
545
+ it 'works like #map' do
546
+ p = Promise.new
547
+ f = p.future.then { |v| v * 2 }
548
+ p.fulfill(3)
549
+ f.value.should == 3 * 2
550
+ end
551
+ end
552
+
553
+ it 'returns a failed future when the block raises an error' do
465
554
  p = Promise.new
466
- f = p.future.flat_map { |v| raise 'Hurgh' }
467
- p.fulfill(3)
468
- expect { f.value }.to raise_error('Hurgh')
555
+ f = p.future.then { |v| raise 'blurgh' }
556
+ d = delayed do
557
+ p.fulfill
558
+ end
559
+ d.value
560
+ expect { f.value }.to raise_error('blurgh')
469
561
  end
470
562
  end
471
563
 
472
564
  describe '#recover' do
473
565
  context 'returns a new future that' do
474
- it 'becomes fulfilled with a value created by the block when the source future fails' do
566
+ it 'resolves to a value created by the block when the source future fails' do
475
567
  p = Promise.new
476
568
  f = p.future.recover { 'foo' }
477
569
  p.fail(error)
478
570
  f.value.should == 'foo'
479
571
  end
480
572
 
481
- it 'becomes fulfilled with a specfied value when the source future fails' do
573
+ it 'resolves to a specfied value when the source future fails' do
482
574
  p = Promise.new
483
575
  f = p.future.recover('bar')
484
576
  p.fail(error)
485
577
  f.value.should == 'bar'
486
578
  end
487
579
 
488
- it 'becomes fulfilled with a value created by the block even when a value is specified when the source future fails' do
580
+ it 'resovles to a value created by the block even when a value is specified when the source future fails' do
489
581
  p = Promise.new
490
582
  f = p.future.recover('bar') { 'foo' }
491
583
  p.fail(error)
492
584
  f.value.should == 'foo'
493
585
  end
494
586
 
495
- it 'becomes fulfilled with nil value when no value nor block is specified and the source future fails' do
587
+ it 'resolves to nil value when no value nor block is specified and the source future fails' do
496
588
  p = Promise.new
497
589
  f = p.future.recover
498
590
  p.fail(error)
@@ -506,7 +598,7 @@ module Ione
506
598
  f.value.should == error.message
507
599
  end
508
600
 
509
- it 'becomes fulfilled with the value of the source future when the source future is fulfilled' do
601
+ it 'resolves to the value of the source future when the source future is resolved' do
510
602
  p = Promise.new
511
603
  f = p.future.recover { 'foo' }
512
604
  p.fulfill('bar')
@@ -567,6 +659,187 @@ module Ione
567
659
  p1.fail(StandardError.new('fnork'))
568
660
  expect { f.value }.to raise_error('bork')
569
661
  end
662
+
663
+ it 'accepts anything that implements #on_complete as a fallback future' do
664
+ fake_future = double(:fake_future)
665
+ fake_future.stub(:on_complete) { |&listener| listener.call(nil, 'foo') }
666
+ p = Promise.new
667
+ f = p.future.fallback { fake_future }
668
+ p.fail(error)
669
+ f.value.should == 'foo'
670
+ end
671
+ end
672
+ end
673
+
674
+ describe '.traverse' do
675
+ it 'combines Array#map and Future.all' do
676
+ future = Future.traverse([1, 2, 3]) do |element|
677
+ Future.resolved(element * 2)
678
+ end
679
+ future.value.should == [2, 4, 6]
680
+ end
681
+
682
+ it 'fails if any of the source futures fail' do
683
+ future = Future.traverse([1, 2, 3]) do |element|
684
+ if element == 2
685
+ Future.failed(StandardError.new('BORK'))
686
+ else
687
+ Future.resolved(element * 2)
688
+ end
689
+ end
690
+ future.should be_failed
691
+ end
692
+
693
+ it 'fails if any of the block invocations fail' do
694
+ future = Future.traverse([1, 2, 3]) do |element|
695
+ if element == 2
696
+ raise 'BORK'
697
+ else
698
+ Future.resolved(element * 2)
699
+ end
700
+ end
701
+ future.should be_failed
702
+ end
703
+
704
+ it 'accepts anything that implements #on_complete as futures' do
705
+ fake_future = double(:fake_future)
706
+ fake_future.stub(:on_complete) { |&listener| listener.call(nil, :foobar) }
707
+ future = Future.traverse([1, 2, 3]) { fake_future }
708
+ future.value.should == [:foobar, :foobar, :foobar]
709
+ end
710
+
711
+ it 'accepts an enumerable of values' do
712
+ future = Future.traverse([1, 2, 3].to_enum) { |v| Future.resolved(v * 2) }
713
+ future.value.should == [2, 4, 6]
714
+ end
715
+ end
716
+
717
+ describe '.reduce' do
718
+ it 'returns a future which represents the value of reducing the values of the inputs' do
719
+ futures = [
720
+ Future.resolved({'foo' => 'bar'}),
721
+ Future.resolved({'qux' => 'baz'}),
722
+ Future.resolved({'hello' => 'world'})
723
+ ]
724
+ future = Future.reduce(futures, {}) do |accumulator, value|
725
+ accumulator.merge(value)
726
+ end
727
+ future.value.should == {'foo' => 'bar', 'qux' => 'baz', 'hello' => 'world'}
728
+ end
729
+
730
+ it 'calls the block with the values in the order of the source futures' do
731
+ promises = [Promise.new, Promise.new, Promise.new, Promise.new, Promise.new]
732
+ futures = promises.map(&:future)
733
+ future = Future.reduce(futures, []) do |accumulator, value|
734
+ accumulator.push(value)
735
+ end
736
+ promises[1].fulfill(1)
737
+ promises[0].fulfill(0)
738
+ promises[2].fulfill(2)
739
+ promises[4].fulfill(4)
740
+ promises[3].fulfill(3)
741
+ future.value.should == [0, 1, 2, 3, 4]
742
+ end
743
+
744
+ it 'uses the first value as initial value when no intial value is given' do
745
+ promises = [Promise.new, Promise.new, Promise.new]
746
+ futures = promises.map(&:future)
747
+ future = Future.reduce(futures) do |sum, n|
748
+ sum + n
749
+ end
750
+ promises[1].fulfill(2)
751
+ promises[0].fulfill(1)
752
+ promises[2].fulfill(3)
753
+ future.value.should == 6
754
+ end
755
+
756
+ it 'fails if any of the source futures fail' do
757
+ futures = [Future.resolved(0), Future.failed(StandardError.new('BORK')), Future.resolved(2)]
758
+ future = Future.reduce(futures, []) do |accumulator, value|
759
+ accumulator.push(value)
760
+ end
761
+ future.should be_failed
762
+ end
763
+
764
+ it 'fails if any of the block invocations fail' do
765
+ futures = [Future.resolved(0), Future.resolved(1), Future.resolved(2)]
766
+ future = Future.reduce(futures, []) do |accumulator, value|
767
+ if value == 2
768
+ raise 'BORK'
769
+ else
770
+ accumulator.push(value)
771
+ end
772
+ end
773
+ future.should be_failed
774
+ end
775
+
776
+ context 'when the list of futures is empty' do
777
+ it 'returns a future that resolves to the initial value' do
778
+ Future.reduce([], :foo).value.should == :foo
779
+ end
780
+
781
+ it 'returns a future that resolves to nil there is also no initial value' do
782
+ Future.reduce([]).value.should be_nil
783
+ end
784
+ end
785
+
786
+ it 'accepts anything that implements #on_complete as futures' do
787
+ ff1, ff2, ff3 = double, double, double
788
+ ff1.stub(:on_complete) { |&listener| listener.call(nil, 1) }
789
+ ff2.stub(:on_complete) { |&listener| listener.call(nil, 2) }
790
+ ff3.stub(:on_complete) { |&listener| listener.call(nil, 3) }
791
+ future = Future.reduce([ff1, ff2, ff3], 0) { |sum, n| sum + n }
792
+ future.value.should == 6
793
+ end
794
+
795
+ it 'accepts an enumerable of futures' do
796
+ futures = [Future.resolved(1), Future.resolved(2), Future.resolved(3)].to_enum
797
+ future = Future.reduce(futures, 0) { |sum, n| sum + n }
798
+ future.value.should == 6
799
+ end
800
+
801
+ context 'when the :ordered option is false' do
802
+ it 'calls the block with the values in the order of completion, when the :ordered option is false' do
803
+ promises = [Promise.new, Promise.new, Promise.new]
804
+ futures = promises.map(&:future)
805
+ future = Future.reduce(futures, [], ordered: false) do |accumulator, value|
806
+ accumulator.push(value)
807
+ end
808
+ promises[1].fulfill(1)
809
+ promises[0].fulfill(0)
810
+ promises[2].fulfill(2)
811
+ future.value.should == [1, 0, 2]
812
+ end
813
+
814
+ it 'fails if any of the source futures fail' do
815
+ futures = [Future.resolved(0), Future.failed(StandardError.new('BORK')), Future.resolved(2)]
816
+ future = Future.reduce(futures, [], ordered: false) do |accumulator, value|
817
+ accumulator.push(value)
818
+ end
819
+ future.should be_failed
820
+ end
821
+
822
+ it 'fails if any of the block invocations fail' do
823
+ futures = [Future.resolved(0), Future.resolved(1), Future.resolved(2)]
824
+ future = Future.reduce(futures, [], ordered: false) do |accumulator, value|
825
+ if value == 1
826
+ raise 'BORK'
827
+ else
828
+ accumulator.push(value)
829
+ end
830
+ end
831
+ future.should be_failed
832
+ end
833
+
834
+ context 'when the list of futures is empty' do
835
+ it 'returns a future that resolves to the initial value' do
836
+ Future.reduce([], :foo, ordered: false).value.should == :foo
837
+ end
838
+
839
+ it 'returns a future that resolves to nil there is also no initial value' do
840
+ Future.reduce([], nil, ordered: false).value.should be_nil
841
+ end
842
+ end
570
843
  end
571
844
  end
572
845
 
@@ -582,22 +855,7 @@ module Ione
582
855
  f.should be_resolved
583
856
  end
584
857
 
585
- it 'resolves when the source futures are resolved' do
586
- sequence = []
587
- p1 = Promise.new
588
- p2 = Promise.new
589
- p3 = Promise.new
590
- f = Future.all(p1.future, p2.future, p3.future)
591
- p1.future.on_value { sequence << 1 }
592
- p2.future.on_value { sequence << 2 }
593
- p3.future.on_value { sequence << 3 }
594
- p2.fulfill
595
- p1.fulfill
596
- p3.fulfill
597
- sequence.should == [2, 1, 3]
598
- end
599
-
600
- it 'returns an array of the values of the source futures, in order ' do
858
+ it 'returns an array of the values of the source futures, in order' do
601
859
  p1 = Promise.new
602
860
  p2 = Promise.new
603
861
  p3 = Promise.new
@@ -630,6 +888,31 @@ module Ione
630
888
  f = Future.resolved(1)
631
889
  Future.all(f).value.should == [1]
632
890
  end
891
+
892
+ it 'accepts a list of futures' do
893
+ promises = [Promise.new, Promise.new, Promise.new]
894
+ futures = promises.map(&:future)
895
+ f = Future.all(futures)
896
+ promises.each(&:fulfill)
897
+ f.value.should have(3).items
898
+ end
899
+
900
+ it 'accepts an enumerable of futures' do
901
+ promises = [Promise.new, Promise.new, Promise.new]
902
+ futures = promises.map(&:future).to_enum
903
+ f = Future.all(futures)
904
+ promises.each(&:fulfill)
905
+ f.value.should have(3).items
906
+ end
907
+
908
+ it 'accepts anything that implements #on_complete as futures' do
909
+ ff1, ff2, ff3 = double, double, double
910
+ ff1.stub(:on_complete) { |&listener| listener.call(nil, 1) }
911
+ ff2.stub(:on_complete) { |&listener| listener.call(nil, 2) }
912
+ ff3.stub(:on_complete) { |&listener| listener.call(nil, 3) }
913
+ future = Future.all(ff1, ff2, ff3)
914
+ future.value.should == [1, 2, 3]
915
+ end
633
916
  end
634
917
  end
635
918
 
@@ -704,6 +987,30 @@ module Ione
704
987
  it 'completes with the value of the given future, when only one is given' do
705
988
  Future.first(Future.resolved('foo')).value.should == 'foo'
706
989
  end
990
+
991
+ it 'accepts a list of futures' do
992
+ promises = [Promise.new, Promise.new, Promise.new]
993
+ futures = promises.map(&:future)
994
+ f = Future.first(futures)
995
+ promises.each(&:fulfill)
996
+ f.should be_resolved
997
+ end
998
+
999
+ it 'accepts an enumerable of futures' do
1000
+ promises = [Promise.new, Promise.new, Promise.new]
1001
+ futures = promises.map(&:future).to_enum
1002
+ f = Future.first(futures)
1003
+ promises.each(&:fulfill)
1004
+ f.should be_resolved
1005
+ end
1006
+
1007
+ it 'accepts anything that implements #on_complete as futures' do
1008
+ ff1, ff2 = double, double
1009
+ ff1.stub(:on_complete) { |&listener| listener.call(nil, 1) }
1010
+ ff2.stub(:on_complete) { |&listener| listener.call(nil, 2) }
1011
+ future = Future.first(ff1, ff2)
1012
+ future.value.should == 1
1013
+ end
707
1014
  end
708
1015
  end
709
1016
 
@@ -732,9 +1039,10 @@ module Ione
732
1039
  end
733
1040
 
734
1041
  it 'calls its complete callbacks immediately' do
735
- f = nil
736
- future.on_complete { |ff| f = ff }
737
- f.should_not be_nil
1042
+ f, v = nil, nil
1043
+ future.on_complete { |ff, vv| f = ff; v = vv }
1044
+ f.should equal(future)
1045
+ v.should == 'hello world'
738
1046
  end
739
1047
 
740
1048
  it 'does not block on #value' do
@@ -772,9 +1080,10 @@ module Ione
772
1080
  end
773
1081
 
774
1082
  it 'calls its complete callbacks immediately' do
775
- f = nil
776
- future.on_complete { |ff| f = ff }
777
- f.should_not be_nil
1083
+ f, e = nil, nil
1084
+ future.on_complete { |ff, _, ee| f = ff; e = ee }
1085
+ f.should equal(future)
1086
+ e.message.should == 'bork'
778
1087
  end
779
1088
 
780
1089
  it 'does not block on #value' do
@@ -340,6 +340,20 @@ module Ione
340
340
  loop_body.tick
341
341
  end
342
342
 
343
+ it 'does nothing when IO.select raises Errno::EBADF' do
344
+ selector.should_receive(:select) do
345
+ raise Errno::EBADF
346
+ end
347
+ loop_body.tick
348
+ end
349
+
350
+ it 'does nothing when IO.select raises IOError' do
351
+ selector.should_receive(:select) do
352
+ raise IOError
353
+ end
354
+ loop_body.tick
355
+ end
356
+
343
357
  it 'calls #read on all readable sockets returned by the selector' do
344
358
  socket.stub(:connected?).and_return(true)
345
359
  socket.should_receive(:read)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ione
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.2.0.pre0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Theo Hultberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-26 00:00:00.000000000 Z
11
+ date: 2014-08-20 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Reactive programming framework for Ruby, painless evented IO, futures
14
14
  and an efficient byte buffer
@@ -56,9 +56,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
56
56
  version: 1.9.3
57
57
  required_rubygems_version: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - ">"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 1.3.1
62
62
  requirements: []
63
63
  rubyforge_project:
64
64
  rubygems_version: 2.2.2