ione 1.1.5 → 1.2.0.pre0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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