ione 1.2.0.pre9 → 1.2.0

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: 269fc51bb786b26f473b2eec82fb37495832842e
4
- data.tar.gz: 7c709b42991c6879f9a0cd1df579e31b158a0d32
3
+ metadata.gz: 7b16fd5d35a6cd0dd75c20ea9a3febd681f5a37b
4
+ data.tar.gz: 5aff84c71ae8a849858789063b528ed59b45c87c
5
5
  SHA512:
6
- metadata.gz: 5e984ed67e16f8dcbcd10671e3033f911b17af68eb5cb2e11231c01dd4dd83fdaff70681e928100226dafbd7fb027ebe64bc20d178a3e216945ce7bd7134a9a2
7
- data.tar.gz: 37dc89c4553b85de563ea94caffc8d07f555e622c1ab3d00170be9dfb6c47ae0d7c754ce6eac09a586eaff3040ad065db6a05e61eca35201737f6ab397a0b96c
6
+ metadata.gz: b6e57d6dc85d7b329353e8d7ff2cb8ba606ff2431377fb7f1e7ca1badf6e0b1ccc7e3971d03cf28387798b307c85cbb32de4c0dbc2851664e096447b190b7215
7
+ data.tar.gz: c6825f4358471a8c344f39f3473dc6c11d8f3367886d25953de255c84ebf53f214098f3deeb46471a323ef1a5b628c72e271a910d6930db5d146bba885ba29e6
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![Coverage Status](https://coveralls.io/repos/iconara/ione/badge.png)](https://coveralls.io/r/iconara/ione)
5
5
  [![Blog](http://b.repl.ca/v1/blog-ione-ff69b4.png)](http://architecturalatrocities.com/tagged/ione)
6
6
 
7
- _If you're reading this on GitHub, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the readme for a specific version either through [rubydoc.info](http://rubydoc.info/find/gems?q=ione) or via the release tags ([here is an example](https://github.com/iconara/ione/tree/v1.0.0.pre0))._
7
+ _If you're reading this on GitHub, please note that this is the readme for the development version and that some features described here might not yet have been released. You can find the readme for a specific version either through [rubydoc.info](http://rubydoc.info/find/gems?q=ione) or via the release tags ([here is an example](https://github.com/iconara/ione/tree/v1.2.0))._
8
8
 
9
9
  Ione is a framework for reactive programming in Ruby. It is based on the reactive core of [cql-rb](http://github.com/iconara/cql-rb), the Ruby driver for Cassandra.
10
10
 
@@ -29,6 +29,7 @@ The [examples](https://github.com/iconara/ione/tree/master/examples) directory h
29
29
  * [redis_client](https://github.com/iconara/ione/tree/master/examples/redis_client) is a more or less full featured Redis client that uses most of Ione's features.
30
30
  * [http_client](https://github.com/iconara/ione/tree/master/examples/http_client) is a simplistic HTTP client that uses Ione and [http_parser.rb](http://rubygems.org/gems/http_parser.rb) to make HTTP GET request.
31
31
  * [cql-rb](https://github.com/iconara/cql-rb) is a high performance Cassandra driver and where Ione was originally developed.
32
+ * [cassandra-driver](https://github.com/datastax/ruby-driver) is the successor to cql-rb.
32
33
  * [ione-rpc](https://github.com/iconara/ione-rpc) is a RPC framework built on Ione. It makes it reasonably easy to build networked applications without having to reinvent the wheel.
33
34
 
34
35
  # How to contribute
data/lib/ione/future.rb CHANGED
@@ -79,362 +79,475 @@ module Ione
79
79
  end
80
80
  end
81
81
 
82
- module FutureFactories
83
- # Combines multiple futures into a new future which resolves when all
84
- # constituent futures complete, or fails when one or more of them fails.
85
- #
86
- # The value of the combined future is an array of the values of the
87
- # constituent futures.
88
- #
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)
91
- # @return [Ione::Future<Array>] an array of the values of the constituent
92
- # futures
93
- def all(*futures)
94
- if futures.size == 1 && (fs = futures.first).is_a?(Enumerable)
95
- futures = fs
96
- end
97
- if futures.count == 0
98
- resolved([])
99
- else
100
- CombinedFuture.new(futures)
82
+ # A future represents the value of a process that may not yet have completed.
83
+ #
84
+ # A future is either pending or completed and there are two ways to complete a
85
+ # future: either by resolving it to a value, or by failing it.
86
+ #
87
+ # A future is usually created by first creating a {Promise} and returning that
88
+ # promise's future to the caller. The promise can then be _fulfilled_ which
89
+ # resolves the future – see below for an example of thi.
90
+ #
91
+ # The key thing about futures is that they _compose_. If you have a future
92
+ # you can transform it and combine without waiting for its value to be
93
+ # available. This means that you can model a series of asynchronous operations
94
+ # without worrying about which order they will complete in, or what happens
95
+ # if any of them fail. You can describe the steps you want to happen if all
96
+ # goes well, and add a handler at the end to capture errors, like you can
97
+ # with synchronous code and exception handlers. You can also add steps that
98
+ # recover from failures. See below, and the docs for {Combinators} for examples
99
+ # on how to compose asynchronous operations.
100
+ #
101
+ # The mixins {Combinators}, {Callbacks} and {Factories} contain most of the
102
+ # method you would use to work with futures, and can be used for creating bridges
103
+ # to other futures implementations.
104
+ #
105
+ # @example Creating a future for a blocking operation
106
+ # def find_my_ip
107
+ # promise = Promse.new
108
+ # Thread.start do
109
+ # begin
110
+ # data = JSON.load(open('http://jsonip.org/').read)
111
+ # promise.fulfill(data['ip'])
112
+ # rescue => e
113
+ # promise.fail(e)
114
+ # end
115
+ # end
116
+ # promise.future
117
+ # end
118
+ #
119
+ # @example Transforming futures
120
+ # # find_my_ip is the method from the example above
121
+ # ip_future = find_my_ip
122
+ # # Future#map returns a new future that resolves to the value returned by the
123
+ # # block, but the block is not called immediately, but when the receiving
124
+ # # future resolves – this means that we can descrbe the processing steps that
125
+ # # should be performed without having to worry about when the value becomes
126
+ # # available.
127
+ # ipaddr_future = ip_future.map { |ip| IPAddr.new(ip) }
128
+ #
129
+ # @example Composing asynchronous operations
130
+ # # find_my_ip is the method from the example above
131
+ # ip_future = find_my_ip
132
+ # # Future#flat_map is a way of chaining asynchronous operations, the future
133
+ # # it returns will resolve to the value of the future returned by the block,
134
+ # # but the block is not called until the receiver future resolves
135
+ # location_future = ip_future.flat_map do |ip|
136
+ # # assuming resolve_geoip is a method returning a future
137
+ # resolve_geoip(ip)
138
+ # end
139
+ # # scheduler here could be an instance of Ione::Io::IoReactor
140
+ # timer_future = scheduler.schedule_timer(5)
141
+ # # Future.first returns a future that will resolve to the value of the
142
+ # # first of its children that completes, so you can use it in combination
143
+ # # with a scheduler to make sure you don't wait forever
144
+ # location_or_timeout_future = Future.first(location_future, timer_future)
145
+ # location_or_timeout_future.on_value do |location|
146
+ # if location
147
+ # puts "My location is #{location}"
148
+ # end
149
+ # end
150
+ #
151
+ # @example Making requests in parallel and collecting the results
152
+ # # assuming client is a client for a remote service and that #find returns
153
+ # # a future, and that thing_ids is an array of IDs of things we want to load
154
+ # futures = thing_idss.map { |id| client.find(id) }
155
+ # # Future.all is a way to combine multiple futures into a future that resolves
156
+ # # to an array of values, in other words, it takes an array of futures and
157
+ # # resolves to an array of the values of those futures
158
+ # future_of_all_things = Future.all(futures)
159
+ # future_of_all_things.on_value do |things|
160
+ # things.each do |thing|
161
+ # puts "here's a thing: #{thing}"
162
+ # end
163
+ # end
164
+ #
165
+ # @example Another way of making requests in parallel and collecting the results
166
+ # # the last example can be simplified by using Future.traverse, which combines
167
+ # # Array#map with Future.all – the block will be called once per item in
168
+ # # the array, and the returned future resolves to an array of the values of
169
+ # # the futures returned by the block
170
+ # future_of_all_things = Future.traverse(thing_ids) { |id| client.find(id) }
171
+ # future_of_all_things.on_value do |things|
172
+ # things.each do |thing|
173
+ # puts "here's a thing: #{thing}"
174
+ # end
175
+ # end
176
+ #
177
+ # @see Ione::Promise
178
+ # @see Ione::Future::FutureCallbacks
179
+ # @see Ione::Future::FutureCombinators
180
+ # @see Ione::Future::FutureFactories
181
+ class Future
182
+ module Factories
183
+ # Combines multiple futures into a new future which resolves when all
184
+ # constituent futures complete, or fails when one or more of them fails.
185
+ #
186
+ # The value of the combined future is an array of the values of the
187
+ # constituent futures.
188
+ #
189
+ # @example
190
+ # ids = [1, 2, 3, 4]
191
+ # futures = ids.map { |id| find_thing(id) }
192
+ # future = Future.all(ids)
193
+ # future.value # => [thing1, thing2, thing3, thing4]
194
+ #
195
+ # @param [Array<Ione::Future>] futures the futures to combine (this argument
196
+ # can be a splatted array or a regular array passed as sole argument)
197
+ # @return [Ione::Future<Array>] an array of the values of the constituent
198
+ # futures
199
+ def all(*futures)
200
+ if futures.size == 1 && (fs = futures.first).is_a?(Enumerable)
201
+ futures = fs
202
+ end
203
+ if futures.count == 0
204
+ resolved([])
205
+ else
206
+ CombinedFuture.new(futures)
207
+ end
101
208
  end
102
- end
103
209
 
104
- # Returns a future which will be resolved with the value of the first
105
- # (resolved) of the specified futures. If all of the futures fail, the
106
- # returned future will also fail (with the error of the last failed future).
107
- #
108
- # @param [Array<Ione::Future>] futures the futures to monitor (this argument
109
- # can be a splatted array or a regular array passed as sole argument)
110
- # @return [Ione::Future] a future which represents the first completing future
111
- def first(*futures)
112
- if futures.size == 1 && (fs = futures.first).is_a?(Enumerable)
113
- futures = fs
114
- end
115
- if futures.count == 0
116
- resolved
117
- else
118
- FirstFuture.new(futures)
210
+ # Returns a future which will be resolved with the value of the first
211
+ # (resolved) of the specified futures. If all of the futures fail, the
212
+ # returned future will also fail (with the error of the last failed future).
213
+ #
214
+ # @example Speculative execution
215
+ # # make a call to multiple services and use the value of the one that
216
+ # # responds first – and discard the other results
217
+ # f1 = service1.find_thing(id)
218
+ # f2 = service2.find_thing(id)
219
+ # f3 = service3.find_thing(id)
220
+ # f = Future.first(f1, f2, f3)
221
+ # f.value # => the value of the call that was quickest
222
+ #
223
+ # @param [Array<Ione::Future>] futures the futures to monitor (this argument
224
+ # can be a splatted array or a regular array passed as sole argument)
225
+ # @return [Ione::Future] a future which represents the first completing future
226
+ def first(*futures)
227
+ if futures.size == 1 && (fs = futures.first).is_a?(Enumerable)
228
+ futures = fs
229
+ end
230
+ if futures.count == 0
231
+ resolved
232
+ else
233
+ FirstFuture.new(futures)
234
+ end
119
235
  end
120
- end
121
-
122
- # Takes calls the block once for each element in an array, expecting each
123
- # invocation to return a future, and returns a future that resolves to
124
- # an array of the values of those futures.
125
- #
126
- # @example
127
- # ids = [1, 2, 3]
128
- # future = Future.traverse(ids) { |id| load_thing(id) }
129
- # future.value # => [thing1, thing2, thing3]
130
- #
131
- # @param [Array<Object>] values an array whose elements will be passed to
132
- # the block, one by one
133
- # @yieldparam [Object] value each element from the array
134
- # @yieldreturn [Ione::Future] a future
135
- # @return [Ione::Future] a future that will resolve to an array of the values
136
- # of the futures returned by the block
137
- def traverse(values, &block)
138
- all(values.map(&block))
139
- rescue => e
140
- failed(e)
141
- end
142
236
 
143
- # Returns a future that will resolve to a value which is the reduction of
144
- # the values of a list of source futures.
145
- #
146
- # This is essentially a parallel, streaming version of {Enumerable#reduce},
147
- # but for futures. Use this method for example when you want to do a number
148
- # of asynchronous operations in parallel and then merge the results together
149
- # when all are done.
150
- #
151
- # The block will not be called concurrently, which means that unless you're
152
- # handling the initial value or other values in the scope of the block you
153
- # don't need (and shouldn't) do any locking to ensure that the accumulator
154
- # passed to the block is safe to modify. It is, of course, even better if
155
- # you don't modify the accumulator, but return a new, immutable value on
156
- # each invocation.
157
- #
158
- # @example Merging the results of multipe asynchronous calls
159
- # futures = ... # a list of futures that will resolve to hashes
160
- # merged_future = Future.reduce(futures, {}) do |accumulator, value|
161
- # accumulator.merge(value)
162
- # end
163
- #
164
- # @example Reducing with an associative and commutative function, like addition
165
- # futures = ... # a list of futures that will resolve to numbers
166
- # sum_future = Future.reduce(futures, 0, ordered: false) do |accumulator, value|
167
- # accumulator + value
168
- # end
169
- #
170
- # @param [Array<Ione::Future>] futures an array of futures whose values
171
- # should be reduced
172
- # @param [Object] initial_value the initial value of the accumulator
173
- # @param [Hash] options
174
- # @option options [Boolean] :ordered (true) whether or not to respect the
175
- # order of the input when reducing – when true the block will be called
176
- # with the values of the source futures in the order they have in the
177
- # given list, when false the block will be called in the order that the
178
- # futures resolve (which means that your reducer function needs to be
179
- # associative and commutative).
180
- # @yieldparam [Object] accumulator the value of the last invocation of the
181
- # block, or the initial value if this is the first invocation
182
- # @yieldparam [Object] value the value of one of the source futures
183
- # @yieldreturn [Object] the value to pass as accumulator to the next
184
- # invocation of the block
185
- # @return [Ione::Future] a future that will resolve to the value returned
186
- # from the last invocation of the block, or nil when the list of futures
187
- # is empty.
188
- def reduce(futures, initial_value=nil, options=nil, &reducer)
189
- if options && options[:ordered] == false
190
- UnorderedReducingFuture.new(futures, initial_value, reducer)
191
- else
192
- OrderedReducingFuture.new(futures, initial_value, reducer)
237
+ # Takes calls the block once for each element in an array, expecting each
238
+ # invocation to return a future, and returns a future that resolves to
239
+ # an array of the values of those futures.
240
+ #
241
+ # @example
242
+ # ids = [1, 2, 3]
243
+ # future = Future.traverse(ids) { |id| find_thing(id) }
244
+ # future.value # => [thing1, thing2, thing3]
245
+ #
246
+ # @param [Array<Object>] values an array whose elements will be passed to
247
+ # the block, one by one
248
+ # @yieldparam [Object] value each element from the array
249
+ # @yieldreturn [Ione::Future] a future
250
+ # @return [Ione::Future] a future that will resolve to an array of the values
251
+ # of the futures returned by the block
252
+ def traverse(values, &block)
253
+ all(values.map(&block))
254
+ rescue => e
255
+ failed(e)
193
256
  end
194
- end
195
-
196
- # Creates a new pre-resolved future.
197
- #
198
- # @param [Object, nil] value the value of the created future
199
- # @return [Ione::Future] a resolved future
200
- def resolved(value=nil)
201
- return ResolvedFuture::NIL if value.nil?
202
- ResolvedFuture.new(value)
203
- end
204
-
205
- # Creates a new pre-failed future.
206
- #
207
- # @param [Error] error the error of the created future
208
- # @return [Ione::Future] a failed future
209
- def failed(error)
210
- FailedFuture.new(error)
211
- end
212
- end
213
257
 
214
- module FutureCombinators
215
- # Returns a new future representing a transformation of this future's value.
216
- #
217
- # @example
218
- # future2 = future1.map { |value| value * 2 }
219
- #
220
- # @param [Object] value the value of this future (when no block is given)
221
- # @yieldparam [Object] value the value of this future
222
- # @yieldreturn [Object] the transformed value
223
- # @return [Ione::Future] a new future representing the transformed value
224
- def map(value=nil, &block)
225
- f = CompletableFuture.new
226
- on_complete do |v, e|
227
- if e
228
- f.fail(e)
258
+ # Returns a future that will resolve to a value which is the reduction of
259
+ # the values of a list of source futures.
260
+ #
261
+ # This is essentially a parallel, streaming version of `Enumerable#reduce`,
262
+ # but for futures. Use this method for example when you want to do a number
263
+ # of asynchronous operations in parallel and then merge the results together
264
+ # when all are done.
265
+ #
266
+ # The block will not be called concurrently, which means that unless you're
267
+ # handling the initial value or other values in the scope of the block you
268
+ # don't need (and shouldn't) do any locking to ensure that the accumulator
269
+ # passed to the block is safe to modify. It is, of course, even better if
270
+ # you don't modify the accumulator, but return a new, immutable value on
271
+ # each invocation.
272
+ #
273
+ # @example Merging the results of multipe asynchronous calls
274
+ # futures = ... # a list of futures that will resolve to hashes
275
+ # merged_future = Future.reduce(futures, {}) do |accumulator, value|
276
+ # accumulator.merge(value)
277
+ # end
278
+ # merged_future.value # => the result of {}.merge(hash1).merge(hash2), etc.
279
+ #
280
+ # @example Reducing with an associative and commutative function, like addition
281
+ # futures = ... # a list of futures that will resolve to numbers
282
+ # sum_future = Future.reduce(futures, 0, ordered: false) do |accumulator, value|
283
+ # accumulator + value
284
+ # end
285
+ # sum_future.value # => the sum of all values
286
+ #
287
+ # @param [Array<Ione::Future>] futures an array of futures whose values
288
+ # should be reduced
289
+ # @param [Object] initial_value the initial value of the accumulator
290
+ # @param [Hash] options
291
+ # @option options [Boolean] :ordered (true) whether or not to respect the
292
+ # order of the input when reducing – when true the block will be called
293
+ # with the values of the source futures in the order they have in the
294
+ # given list, when false the block will be called in the order that the
295
+ # futures resolve (which means that your reducer function needs to be
296
+ # associative and commutative).
297
+ # @yieldparam [Object] accumulator the value of the last invocation of the
298
+ # block, or the initial value if this is the first invocation
299
+ # @yieldparam [Object] value the value of one of the source futures
300
+ # @yieldreturn [Object] the value to pass as accumulator to the next
301
+ # invocation of the block
302
+ # @return [Ione::Future] a future that will resolve to the value returned
303
+ # from the last invocation of the block, or nil when the list of futures
304
+ # is empty.
305
+ def reduce(futures, initial_value=nil, options=nil, &reducer)
306
+ if options && options[:ordered] == false
307
+ UnorderedReducingFuture.new(futures, initial_value, reducer)
229
308
  else
230
- begin
231
- f.resolve(block ? block.call(v) : value)
232
- rescue => e
233
- f.fail(e)
234
- end
309
+ OrderedReducingFuture.new(futures, initial_value, reducer)
235
310
  end
236
311
  end
237
- f
312
+
313
+ # Creates a new pre-resolved future.
314
+ #
315
+ # @param [Object, nil] value the value of the created future
316
+ # @return [Ione::Future] a resolved future
317
+ def resolved(value=nil)
318
+ return ResolvedFuture::NIL if value.nil?
319
+ ResolvedFuture.new(value)
320
+ end
321
+
322
+ # Creates a new pre-failed future.
323
+ #
324
+ # @param [Error] error the error of the created future
325
+ # @return [Ione::Future] a failed future
326
+ def failed(error)
327
+ FailedFuture.new(error)
328
+ end
238
329
  end
239
330
 
240
- # Returns a new future representing a transformation of this future's value,
241
- # but where the transformation itself may be asynchronous.
242
- #
243
- # @example
244
- # future2 = future1.flat_map { |value| method_returning_a_future(value) }
245
- #
246
- # This method is useful when you want to chain asynchronous operations.
247
- #
248
- # @yieldparam [Object] value the value of this future
249
- # @yieldreturn [Ione::Future] a future representing the transformed value
250
- # @return [Ione::Future] a new future representing the transformed value
251
- def flat_map(&block)
252
- f = CompletableFuture.new
253
- on_complete do |v, e|
254
- if e
255
- f.fail(e)
256
- else
257
- begin
258
- ff = block.call(v)
259
- ff.on_complete do |vv, ee|
260
- if ee
261
- f.fail(ee)
262
- else
263
- f.resolve(vv)
264
- end
265
- end
266
- rescue => e
331
+ module Combinators
332
+ # Returns a new future representing a transformation of this future's value.
333
+ #
334
+ # @example
335
+ # future2 = future1.map { |value| value * 2 }
336
+ #
337
+ # @param [Object] value the value of this future (when no block is given)
338
+ # @yieldparam [Object] value the value of this future
339
+ # @yieldreturn [Object] the transformed value
340
+ # @return [Ione::Future] a new future representing the transformed value
341
+ def map(value=nil, &block)
342
+ f = CompletableFuture.new
343
+ on_complete do |v, e|
344
+ if e
267
345
  f.fail(e)
346
+ else
347
+ begin
348
+ f.resolve(block ? block.call(v) : value)
349
+ rescue => e
350
+ f.fail(e)
351
+ end
268
352
  end
269
353
  end
354
+ f
270
355
  end
271
- f
272
- end
273
356
 
274
- # Returns a new future representing a transformation of this future's value,
275
- # similarily to {#map}, but acts as {#flat_map} when the block returns a
276
- # {Future}.
277
- #
278
- # This method is useful when you want to transform the value of a future,
279
- # but whether or not it can be done synchronously or require an asynchronous
280
- # operation depends on the value of the future.
281
- #
282
- # @example
283
- # future1 = load_something
284
- # future2 = future1.then do |result|
285
- # if result.empty?
286
- # # make a new async call to load fallback value
287
- # load_something_else
288
- # else
289
- # result
290
- # end
291
- # end
292
- #
293
- # @yieldparam [Object] value the value of this future
294
- # @yieldreturn [Object, Ione::Future] the transformed value, or a future
295
- # that will resolve to the transformed value.
296
- # @return [Ione::Future] a new future representing the transformed value
297
- def then(&block)
298
- f = CompletableFuture.new
299
- on_complete do |v, e|
300
- if e
301
- f.fail(e)
302
- else
303
- begin
304
- fv = block.call(v)
305
- if fv.respond_to?(:on_complete)
306
- fv.on_complete do |vv, ee|
357
+ # Returns a new future representing a transformation of this future's value,
358
+ # but where the transformation itself may be asynchronous.
359
+ #
360
+ # @example
361
+ # future2 = future1.flat_map { |value| method_returning_a_future(value) }
362
+ #
363
+ # This method is useful when you want to chain asynchronous operations.
364
+ #
365
+ # @yieldparam [Object] value the value of this future
366
+ # @yieldreturn [Ione::Future] a future representing the transformed value
367
+ # @return [Ione::Future] a new future representing the transformed value
368
+ def flat_map(&block)
369
+ f = CompletableFuture.new
370
+ on_complete do |v, e|
371
+ if e
372
+ f.fail(e)
373
+ else
374
+ begin
375
+ ff = block.call(v)
376
+ ff.on_complete do |vv, ee|
307
377
  if ee
308
378
  f.fail(ee)
309
379
  else
310
380
  f.resolve(vv)
311
381
  end
312
382
  end
313
- else
314
- f.resolve(fv)
383
+ rescue => e
384
+ f.fail(e)
315
385
  end
316
- rescue => e
317
- f.fail(e)
318
386
  end
319
387
  end
388
+ f
320
389
  end
321
- f
322
- end
323
390
 
324
- # Returns a new future which represents either the value of the original
325
- # future, or the result of the given block, if the original future fails.
326
- #
327
- # This method is similar to {#map}, but is triggered by a failure. You can
328
- # also think of it as a `rescue` block for asynchronous operations.
329
- #
330
- # If the block raises an error a failed future with that error will be
331
- # returned (this can be used to transform an error into another error,
332
- # instead of tranforming an error into a value).
333
- #
334
- # @example
335
- # future2 = future1.recover { |error| 'foo' }
336
- # future1.fail(error)
337
- # future2.value # => 'foo'
338
- #
339
- # @param [Object] value the value when no block is given
340
- # @yieldparam [Object] error the error from the original future
341
- # @yieldreturn [Object] the value of the new future
342
- # @return [Ione::Future] a new future representing a value recovered from the error
343
- def recover(value=nil, &block)
344
- f = CompletableFuture.new
345
- on_complete do |v, e|
346
- if e
347
- begin
348
- f.resolve(block ? block.call(e) : value)
349
- rescue => e
391
+ # Returns a new future representing a transformation of this future's value,
392
+ # similarily to {#map}, but acts as {#flat_map} when the block returns a
393
+ # {Future}.
394
+ #
395
+ # This method is useful when you want to transform the value of a future,
396
+ # but whether or not it can be done synchronously or require an asynchronous
397
+ # operation depends on the value of the future.
398
+ #
399
+ # @example
400
+ # future1 = load_something
401
+ # future2 = future1.then do |result|
402
+ # if result.empty?
403
+ # # make a new async call to load fallback value
404
+ # load_something_else
405
+ # else
406
+ # result
407
+ # end
408
+ # end
409
+ #
410
+ # @yieldparam [Object] value the value of this future
411
+ # @yieldreturn [Object, Ione::Future] the transformed value, or a future
412
+ # that will resolve to the transformed value.
413
+ # @return [Ione::Future] a new future representing the transformed value
414
+ def then(&block)
415
+ f = CompletableFuture.new
416
+ on_complete do |v, e|
417
+ if e
350
418
  f.fail(e)
419
+ else
420
+ begin
421
+ fv = block.call(v)
422
+ if fv.respond_to?(:on_complete)
423
+ fv.on_complete do |vv, ee|
424
+ if ee
425
+ f.fail(ee)
426
+ else
427
+ f.resolve(vv)
428
+ end
429
+ end
430
+ else
431
+ f.resolve(fv)
432
+ end
433
+ rescue => e
434
+ f.fail(e)
435
+ end
351
436
  end
352
- else
353
- f.resolve(v)
354
437
  end
438
+ f
355
439
  end
356
- f
357
- end
358
440
 
359
- # Returns a new future which represents either the value of the original
360
- # future, or the value of the future returned by the given block.
361
- #
362
- # This is like {#recover} but for cases when the handling of an error is
363
- # itself asynchronous. In other words, {#fallback} is to {#recover} what
364
- # {#flat_map} is to {#map}.
365
- #
366
- # If the block raises an error a failed future with that error will be
367
- # returned (this can be used to transform an error into another error,
368
- # instead of tranforming an error into a value).
369
- #
370
- # @example
371
- # result = http_get('/foo/bar').fallback do |error|
372
- # http_get('/baz')
373
- # end
374
- # result.value # either the response to /foo/bar, or if that failed
375
- # # the response to /baz
376
- #
377
- # @yieldparam [Object] error the error from the original future
378
- # @yieldreturn [Object] the value of the new future
379
- # @return [Ione::Future] a new future representing a value recovered from the
380
- # error
381
- def fallback(&block)
382
- f = CompletableFuture.new
383
- on_complete do |v, e|
384
- if e
385
- begin
386
- ff = block.call(e)
387
- ff.on_complete do |vv, ee|
388
- if ee
389
- f.fail(ee)
390
- else
391
- f.resolve(vv)
441
+ # Returns a new future which represents either the value of the original
442
+ # future, or the result of the given block, if the original future fails.
443
+ #
444
+ # This method is similar to {#map}, but is triggered by a failure. You can
445
+ # also think of it as a `rescue` block for asynchronous operations.
446
+ #
447
+ # If the block raises an error a failed future with that error will be
448
+ # returned (this can be used to transform an error into another error,
449
+ # instead of tranforming an error into a value).
450
+ #
451
+ # @example
452
+ # future2 = future1.recover { |error| 'foo' }
453
+ # future1.fail(error)
454
+ # future2.value # => 'foo'
455
+ #
456
+ # @param [Object] value the value when no block is given
457
+ # @yieldparam [Object] error the error from the original future
458
+ # @yieldreturn [Object] the value of the new future
459
+ # @return [Ione::Future] a new future representing a value recovered from the error
460
+ def recover(value=nil, &block)
461
+ f = CompletableFuture.new
462
+ on_complete do |v, e|
463
+ if e
464
+ begin
465
+ f.resolve(block ? block.call(e) : value)
466
+ rescue => e
467
+ f.fail(e)
468
+ end
469
+ else
470
+ f.resolve(v)
471
+ end
472
+ end
473
+ f
474
+ end
475
+
476
+ # Returns a new future which represents either the value of the original
477
+ # future, or the value of the future returned by the given block.
478
+ #
479
+ # This is like {#recover} but for cases when the handling of an error is
480
+ # itself asynchronous. In other words, {#fallback} is to {#recover} what
481
+ # {#flat_map} is to {#map}.
482
+ #
483
+ # If the block raises an error a failed future with that error will be
484
+ # returned (this can be used to transform an error into another error,
485
+ # instead of tranforming an error into a value).
486
+ #
487
+ # @example
488
+ # result = http_get('/foo/bar').fallback do |error|
489
+ # http_get('/baz')
490
+ # end
491
+ # result.value # either the response to /foo/bar, or if that failed
492
+ # # the response to /baz
493
+ #
494
+ # @yieldparam [Object] error the error from the original future
495
+ # @yieldreturn [Object] the value of the new future
496
+ # @return [Ione::Future] a new future representing a value recovered from the
497
+ # error
498
+ def fallback(&block)
499
+ f = CompletableFuture.new
500
+ on_complete do |v, e|
501
+ if e
502
+ begin
503
+ ff = block.call(e)
504
+ ff.on_complete do |vv, ee|
505
+ if ee
506
+ f.fail(ee)
507
+ else
508
+ f.resolve(vv)
509
+ end
392
510
  end
511
+ rescue => e
512
+ f.fail(e)
393
513
  end
394
- rescue => e
395
- f.fail(e)
514
+ else
515
+ f.resolve(v)
396
516
  end
397
- else
398
- f.resolve(v)
399
517
  end
518
+ f
400
519
  end
401
- f
402
520
  end
403
- end
404
521
 
405
- module FutureCallbacks
406
- # Registers a listener that will be called when this future becomes
407
- # resolved. The listener will be called with the value of the future as
408
- # sole argument.
409
- #
410
- # @yieldparam [Object] value the value of the resolved future
411
- def on_value(&listener)
412
- on_complete do |value, error|
413
- listener.call(value) unless error
522
+ module Callbacks
523
+ # Registers a listener that will be called when this future becomes
524
+ # resolved. The listener will be called with the value of the future as
525
+ # sole argument.
526
+ #
527
+ # @yieldparam [Object] value the value of the resolved future
528
+ def on_value(&listener)
529
+ on_complete do |value, error|
530
+ listener.call(value) unless error
531
+ end
532
+ nil
414
533
  end
415
- nil
416
- end
417
534
 
418
- # Registers a listener that will be called when this future fails. The
419
- # lisener will be called with the error that failed the future as sole
420
- # argument.
421
- #
422
- # @yieldparam [Error] error the error that failed the future
423
- def on_failure(&listener)
424
- on_complete do |_, error|
425
- listener.call(error) if error
535
+ # Registers a listener that will be called when this future fails. The
536
+ # lisener will be called with the error that failed the future as sole
537
+ # argument.
538
+ #
539
+ # @yieldparam [Error] error the error that failed the future
540
+ def on_failure(&listener)
541
+ on_complete do |_, error|
542
+ listener.call(error) if error
543
+ end
544
+ nil
426
545
  end
427
- nil
428
546
  end
429
- end
430
547
 
431
- # A future represents the value of a process that may not yet have completed.
432
- #
433
- # @see Ione::Promise
434
- class Future
435
- extend FutureFactories
436
- include FutureCombinators
437
- include FutureCallbacks
548
+ extend Factories
549
+ include Combinators
550
+ include Callbacks
438
551
 
439
552
  def initialize
440
553
  @lock = Mutex.new
@@ -444,9 +557,42 @@ module Ione
444
557
 
445
558
  # Registers a listener that will be called when this future completes,
446
559
  # i.e. resolves or fails. The listener will be called with the future as
447
- # solve argument
448
- #
449
- # @yieldparam [Ione::Future] future the future
560
+ # solve argument.
561
+ #
562
+ # The order in which listeners are called is not defined and implementation
563
+ # dependent. The thread the listener will be called on is also not defined
564
+ # and implementation dependent. The default implementation calls listeners
565
+ # registered before completion on the thread that completed the future, and
566
+ # listeners registered after completions on the thread that registers the
567
+ # listener – but this may change in the future, and may be different in
568
+ # special circumstances.
569
+ #
570
+ # When a listener raises an error it will be swallowed and not re-raised.
571
+ # The reason for this is that the processing of the callback may be done
572
+ # in a context that does not expect, nor can recover from, errors. Not
573
+ # swallowing errors would stop other listeners from being called. If it
574
+ # appears as if a listener is not called, first make sure it is not raising
575
+ # any errors (even a syntax error or a spelling mistake in a method or
576
+ # variable name will not be hidden).
577
+ #
578
+ # @note
579
+ # Depending on the arity of the listener it will be passed different
580
+ # arguments. When the listener takes one argument it will receive the
581
+ # future itself as argument (this is backwards compatible with the pre
582
+ # v1.2 behaviour), with two arguments the value and error are given,
583
+ # with three arguments the value, error and the future itself will be
584
+ # given. The listener can also take no arguments. See the tests to find
585
+ # out the nitty-gritty details, for example the behaviour with different
586
+ # combinations of variable arguments and default values.
587
+ #
588
+ # Most of the time you will use {#on_value} and {#on_failure}, and not
589
+ # instead of this method.
590
+ #
591
+ # @yieldparam [Object] value the value that the future resolves to
592
+ # @yieldparam [Error] error the error that failed this future
593
+ # @yieldparam [Ione::Future] future the future itself
594
+ # @see Callbacks#on_value
595
+ # @see Callbacks#on_failure
450
596
  def on_complete(&listener)
451
597
  run_immediately = false
452
598
  if @state != :pending
@@ -475,7 +621,18 @@ module Ione
475
621
  # If the future fails this method will raise the error that failed the
476
622
  # future.
477
623
  #
624
+ # @note
625
+ # This is a blocking operation and should be used with caution. You should
626
+ # never call this method in a block given to any of the other methods
627
+ # on {Future}. Prefer using combinator methods like {#map} and {#flat_map}
628
+ # to compose operations asynchronously, or use {#on_value}, {#on_failure}
629
+ # or {#on_complete} to listen for values and/or failures.
630
+ #
631
+ # @raise [Error] the error that failed this future
478
632
  # @return [Object] the value of this future
633
+ # @see Callbacks#on_value
634
+ # @see Callbacks#on_failure
635
+ # @see Callbacks#on_complete
479
636
  def value
480
637
  raise @error if @state == :failed
481
638
  return @value if @state == :resolved
@@ -556,6 +713,18 @@ module Ione
556
713
  end
557
714
  end
558
715
 
716
+ # @private
717
+ # @deprecated
718
+ FutureCallbacks = Future::Callbacks
719
+
720
+ # @private
721
+ # @deprecated
722
+ FutureCombinators = Future::Combinators
723
+
724
+ # @private
725
+ # @deprecated
726
+ FutureFactories = Future::Factories
727
+
559
728
  # @private
560
729
  class CompletableFuture < Future
561
730
  def resolve(v=nil)
@@ -163,11 +163,11 @@ module Ione
163
163
  #
164
164
  # @param host [String] the host to connect to
165
165
  # @param port [Integer] the port to connect to
166
- # @param options_or_timeout [Hash, Numeric] a hash of options (see below)
166
+ # @param options [Hash, Numeric] a hash of options (see below)
167
167
  # or the connection timeout (equivalent to using the `:timeout` option).
168
- # @option options_or_timeout [Numeric] :timeout (5) the number of seconds
168
+ # @option options [Numeric] :timeout (5) the number of seconds
169
169
  # to wait for a connection before failing
170
- # @option options_or_timeout [Boolean, OpenSSL::SSL::SSLContext] :ssl (false)
170
+ # @option options [Boolean, OpenSSL::SSL::SSLContext] :ssl (false)
171
171
  # pass an `OpenSSL::SSL::SSLContext` to upgrade the connection to SSL,
172
172
  # or true to upgrade the connection and create a new context.
173
173
  # @yieldparam [Ione::Io::Connection] connection the newly opened connection
data/lib/ione/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Ione
4
- VERSION = '1.2.0.pre9'.freeze
4
+ VERSION = '1.2.0'.freeze
5
5
  end
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.2.0.pre9
4
+ version: 1.2.0
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-28 00:00:00.000000000 Z
11
+ date: 2014-10-31 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
@@ -65,9 +65,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
65
65
  version: 1.9.3
66
66
  required_rubygems_version: !ruby/object:Gem::Requirement
67
67
  requirements:
68
- - - ">"
68
+ - - ">="
69
69
  - !ruby/object:Gem::Version
70
- version: 1.3.1
70
+ version: '0'
71
71
  requirements: []
72
72
  rubyforge_project:
73
73
  rubygems_version: 2.2.2