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 +4 -4
- data/README.md +2 -1
- data/lib/ione/future.rb +484 -315
- data/lib/ione/io/io_reactor.rb +3 -3
- data/lib/ione/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7b16fd5d35a6cd0dd75c20ea9a3febd681f5a37b
|
4
|
+
data.tar.gz: 5aff84c71ae8a849858789063b528ed59b45c87c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
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
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
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
|
-
|
314
|
-
f.
|
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
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
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
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
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
|
-
|
395
|
-
f.
|
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
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
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
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
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
|
-
|
432
|
-
|
433
|
-
|
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
|
-
#
|
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)
|
data/lib/ione/io/io_reactor.rb
CHANGED
@@ -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
|
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
|
168
|
+
# @option options [Numeric] :timeout (5) the number of seconds
|
169
169
|
# to wait for a connection before failing
|
170
|
-
# @option
|
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
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
|
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-
|
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:
|
70
|
+
version: '0'
|
71
71
|
requirements: []
|
72
72
|
rubyforge_project:
|
73
73
|
rubygems_version: 2.2.2
|