reacto 0.1.0 → 1.0.0
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 +4 -4
- data/.gitignore +2 -0
- data/README.md +932 -11
- data/doc/reactive_programming_with_reacto.md +238 -0
- data/lib/reacto.rb +70 -0
- data/lib/reacto/behaviours.rb +24 -1
- data/lib/reacto/constants.rb +4 -1
- data/lib/reacto/executors.rb +8 -10
- data/lib/reacto/labeled_trackable.rb +14 -2
- data/lib/reacto/operations.rb +23 -2
- data/lib/reacto/operations/act.rb +69 -0
- data/lib/reacto/operations/append.rb +45 -0
- data/lib/reacto/operations/blocking_enumerable.rb +40 -0
- data/lib/reacto/operations/buffer.rb +1 -4
- data/lib/reacto/operations/chunk.rb +81 -0
- data/lib/reacto/operations/chunk_while.rb +56 -0
- data/lib/reacto/operations/cycle.rb +27 -0
- data/lib/reacto/operations/delay_each.rb +75 -0
- data/lib/reacto/operations/depend_on.rb +4 -5
- data/lib/reacto/operations/diff.rb +8 -10
- data/lib/reacto/operations/drop.rb +6 -8
- data/lib/reacto/operations/drop_while.rb +23 -0
- data/lib/reacto/operations/each_collect.rb +57 -0
- data/lib/reacto/operations/each_with_object.rb +31 -0
- data/lib/reacto/operations/extremums.rb +54 -0
- data/lib/reacto/operations/find_index.rb +28 -0
- data/lib/reacto/operations/flat_map.rb +2 -2
- data/lib/reacto/operations/flatten.rb +2 -7
- data/lib/reacto/operations/flatten_labeled.rb +44 -0
- data/lib/reacto/operations/{label.rb → group_by_label.rb} +1 -1
- data/lib/reacto/operations/include.rb +40 -0
- data/lib/reacto/operations/inject.rb +15 -9
- data/lib/reacto/operations/map.rb +15 -13
- data/lib/reacto/operations/merge.rb +17 -16
- data/lib/reacto/operations/operation_on_labeled.rb +29 -0
- data/lib/reacto/operations/partition.rb +52 -0
- data/lib/reacto/operations/prepend.rb +0 -3
- data/lib/reacto/operations/rescue_and_replace_error.rb +21 -0
- data/lib/reacto/operations/retry.rb +26 -0
- data/lib/reacto/operations/retry_when.rb +30 -0
- data/lib/reacto/operations/select.rb +2 -6
- data/lib/reacto/operations/slice.rb +50 -0
- data/lib/reacto/operations/slice_when.rb +41 -0
- data/lib/reacto/operations/split_labeled.rb +32 -0
- data/lib/reacto/operations/take.rb +9 -14
- data/lib/reacto/operations/take_while.rb +28 -0
- data/lib/reacto/operations/throttle.rb +2 -3
- data/lib/reacto/operations/track_on.rb +1 -3
- data/lib/reacto/shared_trackable.rb +2 -5
- data/lib/reacto/subscriptions/buffered_subscription.rb +10 -9
- data/lib/reacto/subscriptions/executor_subscription.rb +12 -4
- data/lib/reacto/subscriptions/tracker_subscription.rb +0 -4
- data/lib/reacto/subscriptions/zipping_subscription.rb +0 -1
- data/lib/reacto/trackable.rb +429 -64
- data/lib/reacto/version.rb +1 -1
- data/reacto.gemspec +2 -3
- data/spec/reacto/labeled_trackable_spec.rb +17 -0
- data/spec/reacto/trackable/act_spec.rb +15 -0
- data/spec/reacto/trackable/all_spec.rb +38 -0
- data/spec/reacto/trackable/any_spec.rb +39 -0
- data/spec/reacto/trackable/append_spec.rb +38 -0
- data/spec/reacto/trackable/buffer_spec.rb +11 -15
- data/spec/reacto/trackable/chunk_spec.rb +86 -0
- data/spec/reacto/trackable/chunk_while_spec.rb +22 -0
- data/spec/reacto/trackable/class_level/combine_last_spec.rb +1 -3
- data/spec/reacto/trackable/class_level/interval_spec.rb +4 -6
- data/spec/reacto/trackable/class_level/make_spec.rb +0 -15
- data/spec/reacto/trackable/{zip_spec.rb → class_level/zip_spec.rb} +0 -2
- data/spec/reacto/trackable/concat_spec.rb +12 -12
- data/spec/reacto/trackable/count_spec.rb +38 -0
- data/spec/reacto/trackable/cycle_spec.rb +14 -0
- data/spec/reacto/trackable/delay_each_spec.rb +18 -0
- data/spec/reacto/trackable/depend_on_spec.rb +6 -9
- data/spec/reacto/trackable/diff_spec.rb +3 -5
- data/spec/reacto/trackable/drop_errors_spec.rb +1 -3
- data/spec/reacto/trackable/drop_while_spec.rb +15 -0
- data/spec/reacto/trackable/each_cons_spec.rb +53 -0
- data/spec/reacto/trackable/each_slice_spec.rb +37 -0
- data/spec/reacto/trackable/each_with_index_spec.rb +33 -0
- data/spec/reacto/trackable/each_with_object_spec.rb +26 -0
- data/spec/reacto/trackable/entries_spec.rb +25 -0
- data/spec/reacto/trackable/execute_on_spec.rb +33 -0
- data/spec/reacto/trackable/find_index_spec.rb +31 -0
- data/spec/reacto/trackable/find_spec.rb +34 -0
- data/spec/reacto/trackable/first_spec.rb +36 -0
- data/spec/reacto/trackable/flat_map_latest_spec.rb +5 -5
- data/spec/reacto/trackable/flat_map_spec.rb +25 -0
- data/spec/reacto/trackable/flatten_labeled_spec.rb +48 -0
- data/spec/reacto/trackable/grep_spec.rb +29 -0
- data/spec/reacto/trackable/grep_v_spec.rb +23 -0
- data/spec/reacto/trackable/{label_spec.rb → group_by_label_spec.rb} +4 -11
- data/spec/reacto/trackable/include_spec.rb +23 -0
- data/spec/reacto/trackable/inject_spec.rb +30 -4
- data/spec/reacto/trackable/lift_spec.rb +1 -3
- data/spec/reacto/trackable/map_spec.rb +17 -3
- data/spec/reacto/trackable/max_by_spec.rb +12 -0
- data/spec/reacto/trackable/max_spec.rb +19 -0
- data/spec/reacto/trackable/merge_spec.rb +6 -7
- data/spec/reacto/trackable/min_by_spec.rb +12 -0
- data/spec/reacto/trackable/min_spec.rb +19 -0
- data/spec/reacto/trackable/minmax_by_spec.rb +12 -0
- data/spec/reacto/trackable/minmax_spec.rb +19 -0
- data/spec/reacto/trackable/none_spec.rb +38 -0
- data/spec/reacto/trackable/on_spec.rb +11 -4
- data/spec/reacto/trackable/one_spec.rb +38 -0
- data/spec/reacto/trackable/partition_spec.rb +23 -0
- data/spec/reacto/trackable/prepend_spec.rb +1 -3
- data/spec/reacto/trackable/reject_spec.rb +21 -0
- data/spec/reacto/trackable/rescue_and_replace_error_spec.rb +48 -0
- data/spec/reacto/trackable/rescue_and_replace_error_with_spec.rb +26 -0
- data/spec/reacto/trackable/retry_spec.rb +50 -0
- data/spec/reacto/trackable/retry_when_spec.rb +33 -0
- data/spec/reacto/trackable/select_spec.rb +18 -7
- data/spec/reacto/trackable/slice_after_spec.rb +38 -0
- data/spec/reacto/trackable/slice_before_spec.rb +38 -0
- data/spec/reacto/trackable/slice_when_spec.rb +26 -0
- data/spec/reacto/trackable/sort_by_spec.rb +16 -0
- data/spec/reacto/trackable/sort_spec.rb +23 -0
- data/spec/reacto/trackable/split_labeled_spec.rb +37 -0
- data/spec/reacto/trackable/take_while_spec.rb +16 -0
- data/spec/reacto/trackable/throttle_spec.rb +2 -3
- data/spec/reacto/trackable/track_on_spec.rb +2 -3
- data/spec/reacto/trackable/uniq_spec.rb +2 -4
- data/spec/support/helpers.rb +9 -1
- metadata +135 -25
- data/Gemfile.lock +0 -32
- data/lib/reacto/operations/cache.rb +0 -53
- data/spec/reacto/trackable/cache_spec.rb +0 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 72cb45d79605d42cd093b0bfaa2a84d605da0e52
|
4
|
+
data.tar.gz: abf52dd63f1d2f5b78558e607965e7ab2fb0c687
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9a3c9de5616c0bfda47ac8c7f7e192abe298dd4e5413a94f8b594b9d71964550ba975b6d67f64c89437c66c44a26d972147b0aa1f476edc4db8fb9c75c5290c
|
7
|
+
data.tar.gz: e3b42cdcaace06dd7716048b43ca58ef61852e3577a116e3c7d618615fbca4024f2d8be3e894ae83128e4a4de08fd8f3a4fd38dfbc9fcc8f661272fbc7830f65
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -26,26 +26,30 @@ Of course there are other implementations of reactive programming for ruby:
|
|
26
26
|
* [RxRuby](https://github.com/ReactiveX/RxRuby) : Very powerful implementation
|
27
27
|
of RX. Handles concurrency and as a whole has more features than `Reacto`.
|
28
28
|
So why `Reacto`? `Reacto` has simpler interface it is native Ruby lib and
|
29
|
-
is easier to use it. The goal of `Reacto` is to be
|
29
|
+
is easier to use it. The goal of `Reacto` is to be alternative to `RxRuby`
|
30
30
|
in the ruby world.
|
31
31
|
Still the author of Reacto is big fan of `RX` especially `RxJava`. He even has
|
32
32
|
a book on the topic using `RxJava` :
|
33
33
|
[Learning Reactive Programming with Java 8](https://www.packtpub.com/application-development/learning-reactive-programming-java-8)
|
34
34
|
* [Frappuccino](https://github.com/steveklabnik/frappuccino) : Very cool lib,
|
35
35
|
easy to use and simply beautiful. The only drawback - it is a bit limited :
|
36
|
-
no concurrency and small set of
|
37
|
-
But if you don't need more complicated
|
36
|
+
no concurrency and small set of operations.
|
37
|
+
But if you don't need more complicated operations it works.
|
38
38
|
The author of `Reacto` highly recommends it.
|
39
39
|
|
40
40
|
## Usage
|
41
41
|
|
42
42
|
### Simple Trackables
|
43
43
|
|
44
|
-
The main entry point to the
|
45
|
-
It
|
46
|
-
implemenation is pushing notifications to
|
47
|
-
It depends on the source. We can have some remote
|
48
|
-
or an
|
44
|
+
The main entry point to the library is the `Reacto::Trackable` class.
|
45
|
+
It represents something you can track for notifications.
|
46
|
+
Usually a `Reacto::Trackable` implemenation is pushing notifications to
|
47
|
+
some notification tracker. It depends on the source. We can have some remote
|
48
|
+
streaming service as a source or an asynchronous HTTP request or some process
|
49
|
+
pushing updates to another.
|
50
|
+
|
51
|
+
#### value
|
52
|
+
|
49
53
|
Of course the source can be very simple, for example a single value:
|
50
54
|
|
51
55
|
```ruby
|
@@ -53,7 +57,7 @@ Of course the source can be very simple, for example a single value:
|
|
53
57
|
```
|
54
58
|
|
55
59
|
This value won't be emitted as notification until there is no tracker (listener)
|
56
|
-
attached to
|
60
|
+
attached to `trackable` - so this `Trackable` instance is lazy - won't do
|
57
61
|
anything until necessary.
|
58
62
|
|
59
63
|
```ruby
|
@@ -63,12 +67,90 @@ anything until necessary.
|
|
63
67
|
```
|
64
68
|
|
65
69
|
This line attaches a notification tracker to the `trackable` - a lambda that
|
66
|
-
should be called when
|
70
|
+
should be called when `trackable` emits any value. This example is very
|
67
71
|
simple and the `trackable` emits only one value - `5` when a tracker is attached
|
68
|
-
to it so the lambda will be called and the value will be printed.
|
72
|
+
to it so the lambda will be called and the value will be printed. Shortcuts
|
73
|
+
for `Reacto::Trackable.value(val)` are `Reacto.value(val)` and `Reacto[val]`.
|
74
|
+
|
75
|
+
#### error
|
76
|
+
|
77
|
+
If we want to emit only an error notification we can do it with
|
78
|
+
`Trackable.error`. It works the same way as `Trackable.value`,
|
79
|
+
but the notification is of type _error_:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
trackable = Reacto::Trackable.error(StandardError.new('Some error!'))
|
83
|
+
|
84
|
+
trackable.on(error: ->(e) { raise e })
|
85
|
+
```
|
86
|
+
|
87
|
+
Shorcuts for `Reacto::Trackable.error(err)` are `Reacto.error(err)` and
|
88
|
+
`Reacto[err]`. Notice that `Reacto[simple_vlue]` is like `Reacto.value`, but
|
89
|
+
`Reacto[some_standart_error]` is like calling `Reacto.error`.
|
90
|
+
|
91
|
+
#### close
|
92
|
+
|
93
|
+
There is a way to create a `Reacto::Trackable` emitting only _close_
|
94
|
+
notification too:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
trackable = Reacto::Trackable.error(StandardError.new('Some error!'))
|
98
|
+
|
99
|
+
trackable.on(close: ->() { p 'closed' })
|
100
|
+
```
|
101
|
+
|
102
|
+
Shorcuts are `Reacto.close` and `Reacto[:close]`.
|
103
|
+
|
104
|
+
#### enumerable
|
105
|
+
|
106
|
+
Another example is `Trackable` with source an Enumerable instance:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
trackable = Reacto::Trackable.enumerable([1, 3, 4])
|
110
|
+
```
|
111
|
+
|
112
|
+
Again we'll have to call `#on` on it in order to push its values to a tracker.
|
113
|
+
Shorcuts are `Reacto.enumerable(enumerable)` and `Reacto[enumerable]`.
|
114
|
+
|
115
|
+
#### interval
|
116
|
+
|
117
|
+
A neat way to create `Trackable` emitting the values of some _Enumerable_ on
|
118
|
+
every second, for example is `Reacto::Trackable.interval`:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
trackable = described_class.interval(0.3)
|
122
|
+
```
|
123
|
+
|
124
|
+
This one emits the natural numbers on every _0.3_ seconds.
|
125
|
+
The second argument can be an `Enumerator` - limited or unlimited, for example:
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
trackable = described_class.interval(2, ('a'..'z').each)
|
129
|
+
```
|
130
|
+
|
131
|
+
Emits the letters _a to z_ on every two seconds. We can create a custom
|
132
|
+
enumerator and use it.
|
133
|
+
Note that `.interval` creates `Reacto::Trackable` which emits in a special
|
134
|
+
thread, so calling `#on` on it won't block the current thread.
|
135
|
+
Shortcut is `Reacto.interval`.
|
136
|
+
|
137
|
+
#### never
|
138
|
+
|
139
|
+
It is possible that a Trackable which never emits anything is needed. Some
|
140
|
+
operations behave according to `Trackable` instances returned, so a way to have
|
141
|
+
such a `Reacto::Trackable` is:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
trackable = Reacto::Trackable.never
|
145
|
+
```
|
146
|
+
|
147
|
+
Shortcuts for this one are `Reacto.never` and `Reacto[:never]`
|
148
|
+
|
69
149
|
|
70
150
|
### Programming Trackable behavior
|
71
151
|
|
152
|
+
#### make
|
153
|
+
|
72
154
|
A `Reacto::Trackable` can have custom behavior, defining what and when should
|
73
155
|
be sent:
|
74
156
|
|
@@ -86,4 +168,843 @@ be sent:
|
|
86
168
|
When a tracker is attached this behavior will become active and the tracker
|
87
169
|
will receive the first two sentences as values, then, after one second the third
|
88
170
|
one and then a closing notification.
|
171
|
+
Shorcut is `Reacto.make`.
|
172
|
+
|
173
|
+
#### SharedTrackable
|
174
|
+
|
175
|
+
Every time a tracker is attached with call to `on`, this behavior will be
|
176
|
+
executed for the given tracker. If we want to have a shared behavior for all
|
177
|
+
the trackers we can create a `Reacto::SharedTrackable` instance:
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
require 'socket'
|
181
|
+
|
182
|
+
trackable = Reacto::SharedTrackable.make do |subscriber|
|
183
|
+
hostname = 'localhost'
|
184
|
+
port = 3555
|
185
|
+
|
186
|
+
return unless subscriber.subscribed?
|
187
|
+
|
188
|
+
socket = nil
|
189
|
+
begin
|
190
|
+
socket = TCPSocket.open(hostname, port)
|
191
|
+
|
192
|
+
while line = socket.gets
|
193
|
+
break unless subscriber.subscribed?
|
194
|
+
|
195
|
+
subscriber.on_value(line)
|
196
|
+
end
|
197
|
+
|
198
|
+
subscriber.on_close if subscriber.subscribed?
|
199
|
+
rescue StandardError => error
|
200
|
+
subscriber.on_error(error) if subscriber.subscribed?
|
201
|
+
ensure
|
202
|
+
socket.close unless socket.nil?
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
trackable.on(value: -> (v) { puts v })
|
208
|
+
trackable.on do |v|
|
209
|
+
puts v
|
210
|
+
end
|
211
|
+
|
212
|
+
# The above calls of `on` are identical. And the two will the same data.
|
213
|
+
# Nothing happens on calling `on` though, the `trackable` has to be activated:
|
214
|
+
|
215
|
+
trackable.activate!
|
216
|
+
```
|
217
|
+
|
218
|
+
### Tracking for notifications
|
219
|
+
|
220
|
+
#### on
|
221
|
+
|
222
|
+
The easiest way to listen a `Reacto::Trackable` is to call `#on` on it:
|
223
|
+
|
224
|
+
```ruby
|
225
|
+
consumer = -> (value) do
|
226
|
+
# Consume the incoming value
|
227
|
+
end
|
228
|
+
|
229
|
+
trackable.on(value: consumer)
|
230
|
+
```
|
231
|
+
|
232
|
+
Calling it like that will trigger the behavior of `trackable` and
|
233
|
+
all the values it emits, will be passed to the consumer. A block can be passed
|
234
|
+
to `#on` and it will have the same effect:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
trackable.on do |value|
|
238
|
+
# Consume the incoming value
|
239
|
+
end
|
240
|
+
```
|
241
|
+
|
242
|
+
If we want to listen for errors we can call `#on` like that:
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
error_consumer = -> (error) do
|
246
|
+
# Consume the incoming error
|
247
|
+
end
|
248
|
+
|
249
|
+
trackable.on(error: error_consumer)
|
250
|
+
```
|
251
|
+
|
252
|
+
Only one error can be emitted by a `Trackable` for subscription and that will
|
253
|
+
close the `Reacto::Trackable`. If there is no error, the normal closing
|
254
|
+
notification should be emitted. We can fetch it like this:
|
255
|
+
|
256
|
+
```ruby
|
257
|
+
to_be_called_on_close = -> () do
|
258
|
+
# Fnalize?
|
259
|
+
end
|
260
|
+
|
261
|
+
trackable.on(close: to_be_called_on_cloe)
|
262
|
+
```
|
263
|
+
|
264
|
+
#### track
|
265
|
+
|
266
|
+
Under the hood `#on` creates a new `Reacto::Tracker` instance with the right
|
267
|
+
methods. If we want to create our own tracker, we can always call `#track` on
|
268
|
+
the trackable with the given instance:
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
consumer = -> (value) do
|
272
|
+
# Consume the incoming value
|
273
|
+
end
|
274
|
+
error_consumer = -> (error) do
|
275
|
+
# Consume the incoming error
|
276
|
+
end
|
277
|
+
|
278
|
+
trackable.track(Reacto::Trackable.new(
|
279
|
+
value: consumer, error: error_consumer, close: -> () { p 'Closing!' }
|
280
|
+
))
|
281
|
+
```
|
282
|
+
|
283
|
+
All of these keyword parameters have default values - for example if we don't
|
284
|
+
pass a `value:` action, a _no-action_ will be used, doing nothing with the
|
285
|
+
value, the same is right about not passing `close:` action. Be aware that
|
286
|
+
the default `error:` action is raising the error.
|
287
|
+
|
288
|
+
|
289
|
+
### Subscriptions
|
290
|
+
|
291
|
+
Calling `#on` or `#track` will create and return a `Reacto::Subscription`.
|
292
|
+
We can unsubscribe form the `Trackable` with it by calling `#unsubscribe`:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
subscription = trackable.on(value: consumer)
|
296
|
+
|
297
|
+
subscription.unsubscribe
|
298
|
+
```
|
299
|
+
|
300
|
+
This way our notification tracker won't receive notification anymore.
|
301
|
+
Checking if a `Subscription` is subscribed can be done by calling `subscribed?`
|
302
|
+
on it.
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
subscription = trackable.on(value: consumer)
|
306
|
+
|
307
|
+
subscription.subscribed? # true
|
308
|
+
```
|
309
|
+
|
310
|
+
Subscriptions can be used for other things, adding additional subscriptions
|
311
|
+
to them, adding resources, which should be closed on receiving the `close`
|
312
|
+
notification and waiting for a `Trackable` operating on background to finish.
|
313
|
+
|
314
|
+
### Operations
|
315
|
+
|
316
|
+
Operations are methods which can be invoked on a `Reacto::Trackable` instance,
|
317
|
+
and always return a new `Reacto::Trackable` instance. The new trackable has
|
318
|
+
emits all or some of the notifications of the source, somewhat changed by
|
319
|
+
the operation. Let's look at an example:
|
320
|
+
|
321
|
+
#### map
|
322
|
+
|
323
|
+
The `map` operation is a transformation, it transforms every value
|
324
|
+
(and not only), emitted by the source using the _block_ given.
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
source_trackable = Reacto.enumerable((1..100))
|
328
|
+
|
329
|
+
trackable = source_trackable.map { |value| value - 1 }
|
330
|
+
|
331
|
+
trackable.on(value: -> (val) { puts val })
|
332
|
+
# the numbers from 0 to 99 will be printed
|
333
|
+
```
|
334
|
+
|
335
|
+
The `map` operation is able to transform errors as well, it can transform
|
336
|
+
the stream of notifications itself and add new notification before the
|
337
|
+
close notification for example.
|
338
|
+
|
339
|
+
#### select
|
340
|
+
|
341
|
+
The `select` operation filters values using a predicate block:
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
source_trackable = Reacto.enumerable((1..100))
|
345
|
+
|
346
|
+
trackable = source_trackable.select { |value| value % 5 == 0 }
|
347
|
+
|
348
|
+
trackable.on(value: -> (val) { puts val })
|
349
|
+
# the numbers printed will be 5, 10, 15, ... 95, 100
|
350
|
+
```
|
351
|
+
|
352
|
+
There are more filtering operations - `drop`, `take`, `first`, `last`, etc.
|
353
|
+
Look at the specs for examples of them.
|
354
|
+
|
355
|
+
#### inject
|
356
|
+
|
357
|
+
Using `inject` is a way to accumulate and emit data based on the current
|
358
|
+
incoming value and the accumulated data from the previous ones. A better way
|
359
|
+
to explain it is an example:
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
source_trackable = Reacto.enumerable((1..100))
|
363
|
+
|
364
|
+
trackable = source.inject(0) { |prev, v| prev + v }
|
365
|
+
|
366
|
+
trackable.on(value: -> (val) { puts val })
|
367
|
+
# Will print a sequesnce of sums 0+1 then 1+2=3, then 3+4=7, etc, the last
|
368
|
+
# value will be the sum of all the source values
|
369
|
+
```
|
370
|
+
|
371
|
+
Operation similar to `inject` is `diff`, which calls a given block for every
|
372
|
+
two emitted in sequence values and the `Reacto::Trackable` resulting from it,
|
373
|
+
emits this the block's return value. Another one is `each_with_object`, which
|
374
|
+
calls a given block for each value emitted by the source with an arbitrary
|
375
|
+
object given, and emits the initially given object.
|
376
|
+
|
377
|
+
#### flat_map
|
378
|
+
|
379
|
+
This operation takes a block which will be called for every emitted value
|
380
|
+
by the source. The block has to return a `Reacto::Trackable` instance for
|
381
|
+
every value. So if the source emits 10 values, ten Trackable instances will
|
382
|
+
be created, all of which will emit values. All these values are flattened
|
383
|
+
and emitted by the `Reacto::Trackable` created by calling `flat_map`.
|
384
|
+
|
385
|
+
```ruby
|
386
|
+
source = Reacto.enumerable([(-10..-1), [0], (1..10)])
|
387
|
+
trackable = source.flat_map { |v| Reacto[v] }
|
388
|
+
|
389
|
+
trackable.on(value: -> (val) { puts val })
|
390
|
+
# Will print all the numbers from -10 to 10
|
391
|
+
```
|
392
|
+
|
393
|
+
It is a very powerful operation, which allows us to create `Reacto::Trackable`
|
394
|
+
instances from incoming data and write logic using operations on them.
|
395
|
+
|
396
|
+
#### ... and even more operations
|
397
|
+
|
398
|
+
*Reacto* is in continuous development more and more _operations_ are being added
|
399
|
+
to it and there are even more to come.
|
400
|
+
So soon there will be a documentation page for all of the available operations,
|
401
|
+
which will be updated when new ones are added, or existing ones are modified.
|
402
|
+
Keep in mind that `Reacto::Trackable` mirrors `Enumerable`, it even includes
|
403
|
+
it in itself. This means that for every method in `Enumerable`, there is a
|
404
|
+
corresponding operation or method in `Reacto::Trackable`.
|
405
|
+
|
406
|
+
TODO
|
407
|
+
|
408
|
+
### Interacting Trackables
|
409
|
+
|
410
|
+
Trackables can interact with one another, for example one `Reacto::Trackable`
|
411
|
+
instance can be merged with another to produce a new one - emitting the
|
412
|
+
notifications of the two sources.
|
413
|
+
|
414
|
+
#### merge
|
415
|
+
|
416
|
+
This is done by calling `merge` on one of the Trackables and passing to it
|
417
|
+
the other. `merge` is an operation - it produces a new `Reacto::Trackable`
|
418
|
+
instance:
|
419
|
+
|
420
|
+
```ruby
|
421
|
+
trackable = Reacto.interval(2).map { |v| v.to_s + 'a'}.take(5)
|
422
|
+
to_be_merged = Reacto.interval(3.5).map { |v| v.to_s + 'b'}.take(4)
|
423
|
+
|
424
|
+
subscription = trackable.merge(to_be_merged).on(value: -> (val) { puts val })
|
425
|
+
trackable.await(subscription)
|
426
|
+
|
427
|
+
# Something like '0a', '0b', '1a', '2a', '1b', '3a', '4a', '2b', '3b' will
|
428
|
+
# be printed
|
429
|
+
```
|
430
|
+
|
431
|
+
As mentioned before, interval is executed in the background by default, so
|
432
|
+
adding trackers to either of the sources won't block the current thread.
|
433
|
+
This means that the `Reacto::Trackable` created by `merge` will emit the
|
434
|
+
source notifications in the order they are coming and that doesn't depend on
|
435
|
+
which source they are coming from. We call `#await` to it passing the
|
436
|
+
_subscription_ because we don't want the current thread to terminate, we want
|
437
|
+
it to wait for the threads of the two sources to finish. More on that later.
|
438
|
+
|
439
|
+
#### zip
|
440
|
+
|
441
|
+
Zip combines the notifications emitted by multiple `Reacto::Trackable` instances
|
442
|
+
into one, using a combinator function. The first notifications of all the
|
443
|
+
trackables are combined, then the second notifications and when one of the
|
444
|
+
sources emits close/error notification, the one produced by `zip` emits it and
|
445
|
+
closes.
|
446
|
+
|
447
|
+
```ruby
|
448
|
+
source1 = Reacto.interval(3).drop(1).take(4)
|
449
|
+
source2 = Reacto.interval(7, ('a'..'b').each)
|
450
|
+
source3 = Reacto.interval(5, ('A'..'C').each)
|
451
|
+
|
452
|
+
trackable = Reacto::Trackable.zip(source1, source2, source3) do |v1, v2, v3|
|
453
|
+
"#{v1} : #{v2} : #{v3}"
|
454
|
+
end
|
455
|
+
|
456
|
+
subscription = trackable.on(
|
457
|
+
value: -> (val) { puts val }, close: -> () { puts 'Bye!' }
|
458
|
+
)
|
459
|
+
trackable.await(subscription)
|
460
|
+
|
461
|
+
# '1 : a : A' and '2 : b : B' will be printed, then 'Bye!', because the second
|
462
|
+
# source will emit the close notification after emitting 'b'.
|
463
|
+
```
|
464
|
+
|
465
|
+
Here the first source - `source1` is emitting the numbers from `0` to infinity
|
466
|
+
on every 3 seconds, we want to drop the `0` and start by emitting `1`, so we
|
467
|
+
drop the first emitted value with `drop(1)`, then we don't want to emit to
|
468
|
+
infinity, so we `take(4)` - only the first `4` numbers, so `1`, `2`, `3` and `4`
|
469
|
+
. This is an example of how to use the positional filtering operations.
|
470
|
+
A shortcut for this one is `Reacto.zip`.
|
471
|
+
|
472
|
+
#### combine
|
473
|
+
|
474
|
+
There is the `combine` operation which behaves in a fashion similar to `zip`
|
475
|
+
but combines the last emitted values with its combinator function on every new
|
476
|
+
value incoming from any source and closes when all of the sources have closed.
|
477
|
+
|
478
|
+
```ruby
|
479
|
+
source1 = Reacto.interval(3).take(4)
|
480
|
+
source2 = Reacto.interval(7, ('a'..'b').each)
|
481
|
+
source3 = Reacto.interval(5, ('A'..'C').each)
|
482
|
+
|
483
|
+
trackable = Reacto::Trackable.combine(source1, source2, source3) do |v1, v2, v3|
|
484
|
+
"#{v1} : #{v2} : #{v3}"
|
485
|
+
end
|
486
|
+
|
487
|
+
subscription = trackable.on(
|
488
|
+
value: -> (val) { puts val }, close: -> () { puts 'Bye!' }
|
489
|
+
)
|
490
|
+
trackable.await(subscription)
|
491
|
+
|
492
|
+
# '1 : a : A', '2 : a : A', '2 : a : B', '3 : a : B', '3 : b : B',
|
493
|
+
# '3 : b : C' and then 'Bye!' will be printed.
|
494
|
+
```
|
495
|
+
|
496
|
+
All of these values will be emitted on the right intervals. For example
|
497
|
+
`'1 : a : A'` will be emitted `7` seconds after the subscription, because `a`
|
498
|
+
takes the most time and the first notification of the combined trackable have
|
499
|
+
to include data from all of the sources. Then the second - `'2 : a : A'` will be
|
500
|
+
emitted the `9th` second from the start, because `2` is emitted on the `9th`
|
501
|
+
second, etc. In the beginning the `0` emitted by the first source is silently
|
502
|
+
skipped.
|
503
|
+
Shortcut for this one is `Reacto.combine`; `Reacto::Trackable.combine_latest`
|
504
|
+
is an alias.
|
505
|
+
|
506
|
+
#### concat
|
507
|
+
|
508
|
+
Concatenating one `Reacto::Trackable` to another, basically means that the
|
509
|
+
resulting `Trackable` will emit the values of the first one, then the values
|
510
|
+
of the second one:
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
source1 = Reacto.enumerable((1..5))
|
514
|
+
source2 = Reacto.enumerable((6..10))
|
515
|
+
|
516
|
+
trackable = source1.concat(source2)
|
517
|
+
|
518
|
+
trackable.on(
|
519
|
+
value: -> (val) { puts val }, close: -> () { puts 'Bye!' }
|
520
|
+
)
|
521
|
+
|
522
|
+
# The values from 1 to 10 will be printed, then 'Bye!'
|
523
|
+
```
|
524
|
+
|
525
|
+
Another way to use it would be
|
526
|
+
`Reacto::Trackable.concat(trackable1, trackable2, ... , trackableN)` with
|
527
|
+
shortcut `Reacto.concat`.
|
528
|
+
|
529
|
+
#### depend_on
|
530
|
+
|
531
|
+
One `Reacto::Trackable`'s notifications can depend on another's accumulated
|
532
|
+
notifications. The `depend_on` operation, expects a `Trackable` and a block,
|
533
|
+
the block is used in the same manner as `inject` uses its block on this passed
|
534
|
+
`Trackable`. When the passed `Trackable` closes, the accumulated data by the
|
535
|
+
block (the last value) is emitted with every notification of the caller:
|
536
|
+
|
537
|
+
```ruby
|
538
|
+
dependency = Reacto.enumerable((1..10))
|
539
|
+
trackable = Reacto.enumerable([5, 4]).depend_on(dependency, &:+)
|
540
|
+
|
541
|
+
trackable.on(
|
542
|
+
value: -> (val) { puts val }, close: -> () { puts 'Bye!' }
|
543
|
+
)
|
544
|
+
|
545
|
+
# The emitted notifications will printed:
|
546
|
+
# Value notification : notification.value: 5, notification.data: 55
|
547
|
+
# Value notification : notification.value: 4, notification.data: 55
|
548
|
+
# Close notification : prints 'Bye!'
|
549
|
+
```
|
550
|
+
|
551
|
+
Without passed block, the first emitted value of the dependency is used as data.
|
552
|
+
If there is an error from the dependency, it is emitted by the caller. The key
|
553
|
+
of the dependency, can be changed from `data` to something else by passing a
|
554
|
+
`key:` to the operation.
|
555
|
+
|
556
|
+
### Concurency
|
557
|
+
|
558
|
+
Aside from factory methods like `.interval` or `.later`, `Reacto::Trackable`
|
559
|
+
has two other ways of emitting its notification concurrently to the thread
|
560
|
+
that created it (or some other thread). Every trackable can do that by using
|
561
|
+
the two dedicated operations `execute_on` and `track_on`.
|
562
|
+
|
563
|
+
#### execute_on
|
564
|
+
|
565
|
+
This operation returns a `Reacto::Trackable` which operations will be executed
|
566
|
+
on the given `executor` plus the operations of its source will be executed on
|
567
|
+
that `executor` as well. Basically this means that the whole logic - the
|
568
|
+
behavior of the first `Trackable` in the chain of operations and all subsequent
|
569
|
+
operations will be executed on the given `executor`.
|
570
|
+
By `executor`, we mean the ones provided by the `Reacto::Executors`'s methods or
|
571
|
+
a custom implementation complying to `concurrent-ruby`'s
|
572
|
+
`Concurrent::ExecutorService`. These executors menage threads for us, some of
|
573
|
+
them are thread pools, which allows us to reuse unused threads from the pool,
|
574
|
+
others provide always new threads on demand or just a single thread. In the
|
575
|
+
following example the `Reacto::Executors.io`
|
576
|
+
executor is used (passed as just `:io`) :
|
577
|
+
|
578
|
+
```ruby
|
579
|
+
require 'net/http'
|
580
|
+
require 'uri'
|
581
|
+
require 'json'
|
582
|
+
|
583
|
+
request_url_behavior = -> (url) do
|
584
|
+
-> (subscriber) do
|
585
|
+
begin
|
586
|
+
response = Net::HTTP.get_response(URI.parse(url))
|
587
|
+
|
588
|
+
if response.code == '200'
|
589
|
+
subscriber.on_value(response.body)
|
590
|
+
subscriber.on_close
|
591
|
+
else
|
592
|
+
subscriber.on_error(StandardError.new(response))
|
593
|
+
end
|
594
|
+
rescue StandardError => e
|
595
|
+
subscriber.on_error(e)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
end
|
599
|
+
|
600
|
+
trackable = Reacto.make(
|
601
|
+
&request_url_behavior.call('https://api.github.com/repos/meddle0x53/reacto')
|
602
|
+
)
|
603
|
+
|
604
|
+
trackable = trackable.map { |response| JSON.parse(response) }
|
605
|
+
|
606
|
+
star_count = trackable.map { |val| val['stargazers_count'] }.map do |val|
|
607
|
+
"#{val} star(s) on Reacto's github page!"
|
608
|
+
end
|
609
|
+
|
610
|
+
star_count = star_count.execute_on(:io)
|
611
|
+
|
612
|
+
star_count_subscription = star_count.on(
|
613
|
+
value: -> (val) { puts val }, close: -> () { puts '---------' }
|
614
|
+
)
|
615
|
+
|
616
|
+
star_gazers = trackable.map { |val| val['stargazers_url'] }.flat_map do |url|
|
617
|
+
Reacto.make(&request_url_behavior.call(url)).map do |response|
|
618
|
+
JSON.parse(response)
|
619
|
+
end.flat_map do |array|
|
620
|
+
Reacto.enumerable(array).map { |data| data['login'] }
|
621
|
+
end
|
622
|
+
end
|
623
|
+
|
624
|
+
star_gazers = star_gazers.execute_on(:io)
|
625
|
+
|
626
|
+
star_gazers_subscription = star_gazers.on(
|
627
|
+
value: -> (val) { puts val }, close: -> () { puts 'Thank you all!' }
|
628
|
+
)
|
629
|
+
|
630
|
+
star_gazers.await(star_gazers_subscription, 60)
|
631
|
+
star_count.await(star_count_subscription, 60)
|
632
|
+
|
633
|
+
```
|
634
|
+
|
635
|
+
This example is a bit silly because it makes two requests to the same URL,
|
636
|
+
but they are concurrent thanks to `execute_on` and that's the important thing.
|
637
|
+
The first trackable reads the number of the stars of this repository and its
|
638
|
+
consumer prints them, and the second one requests the _stargazers_ list, using
|
639
|
+
`flat_map` and prints the names of the star gazers. The two chains are
|
640
|
+
executed concurrently.
|
641
|
+
The `IO` executor is a cached thread pool, which means that threads are reused
|
642
|
+
if available, otherwise new are created, the thread pool does not have fixed
|
643
|
+
size.
|
644
|
+
|
645
|
+
#### track_on
|
646
|
+
|
647
|
+
The difference between `execute_on` and `track_on` is that `track_on` executes
|
648
|
+
on the given executor only the operations positioned after it in the chain.
|
649
|
+
|
650
|
+
```ruby
|
651
|
+
trackable = Reacto.enumerable((1..100)).map { |v| v * 10 }.track_on(:tasks)
|
652
|
+
trackable = trackable.inject(&:+).last
|
653
|
+
|
654
|
+
subscription = trackable.on(
|
655
|
+
value: -> (val) { puts val }, close: -> () { puts 'DONE.' }
|
656
|
+
)
|
657
|
+
|
658
|
+
trackable.await(subscription, 10)
|
659
|
+
```
|
660
|
+
|
661
|
+
Only the sum and `last` will happen on the `tasks` executor - a thread pool
|
662
|
+
with fixed number of threads. The `map` will execute on the current thread.
|
663
|
+
|
664
|
+
#### Executors and factory methods
|
665
|
+
|
666
|
+
Executors can be passed to most of the methods which create `Reacto::Trackable`
|
667
|
+
instances. The methods can receive a keyword argument - `executor:` and will
|
668
|
+
execute the whole trackable chain on it. The same way if it was passed to
|
669
|
+
`execute_on`.
|
670
|
+
|
671
|
+
```ruby
|
672
|
+
trackable = Reacto.enumerable((1..1000), executor: :new_thread)
|
673
|
+
```
|
674
|
+
|
675
|
+
All the operations on called on this `Trackable` and its derivative
|
676
|
+
`Trackable`s will be executed in the `new_thread` executor. This executor
|
677
|
+
creates a new thread always.
|
678
|
+
|
679
|
+
The available executors are:
|
680
|
+
|
681
|
+
* `IO` - can be passed as `:io` or `Executors.io` - an unlimited cached thread
|
682
|
+
pool.
|
683
|
+
* `Tasks` - can be passed as `:tasks`, `:background` and `Executors.tasks` -
|
684
|
+
a thread pool with fixed size.
|
685
|
+
* `New thread` - can be passed as `:new_thread` ot `Executors.new_thread` -
|
686
|
+
always creates a new thread.
|
687
|
+
* `Current` - can be passed as `:immediate`, `:current`, `:now`,
|
688
|
+
`Executors.current` and `Executors.immediate` - uses the current thread to
|
689
|
+
execute operations, does not create a new thread at all.
|
690
|
+
|
691
|
+
### Buffering, delaying and skipping
|
692
|
+
|
693
|
+
There are a few special operations related to buffering incoming notifications
|
694
|
+
and emit notifications consisting of the buffered ones.
|
695
|
+
|
696
|
+
#### buffer
|
697
|
+
|
698
|
+
It is possible to buffer values using a count and then emit them as one array
|
699
|
+
of values.
|
700
|
+
|
701
|
+
```ruby
|
702
|
+
trackable = Reacto.enumerable((1..20)).buffer(count: 5)
|
703
|
+
|
704
|
+
trackable.on(value: -> (val) { p val })
|
705
|
+
|
706
|
+
# Will print [1, 2, 3, 4, 5], then [6, 7, 8, 9, 10], then [11, 12, 13, 14, 15]
|
707
|
+
# and in the end [16, 17, 18 , 19, 20]
|
708
|
+
```
|
709
|
+
|
710
|
+
Buffering helps lowering the number of value notification, when the source is
|
711
|
+
emitting too many of them, too fast.
|
712
|
+
|
713
|
+
#### delay
|
714
|
+
|
715
|
+
Notifications can be buffered using a delay too, for example : don't emit
|
716
|
+
anything from the source for 5 seconds, then emit everything received until
|
717
|
+
now and repeat.
|
718
|
+
|
719
|
+
```ruby
|
720
|
+
trackable = Reacto.interval(1).take(20).buffer(delay: 5)
|
721
|
+
|
722
|
+
subscription = trackable.on(value: -> (val) { p val })
|
723
|
+
trackable.await(subscription)
|
724
|
+
|
725
|
+
# Will print on each 5 seconds something like
|
726
|
+
# [0, 1, 2, 3]
|
727
|
+
# [4, 5, 6, 7, 8]
|
728
|
+
# [9, 10, 11, 12, 13]
|
729
|
+
# [14, 15, 16, 17, 18]
|
730
|
+
# [19]
|
731
|
+
```
|
732
|
+
|
733
|
+
Instead of using `buffer(delay: 5)`, we can use the shortcut `delay(5)`.
|
734
|
+
We can buffer by both count and delay using the `buffer` operation.
|
735
|
+
|
736
|
+
#### throttle
|
737
|
+
|
738
|
+
If too many notifications are received too fast, sometimes it is better to
|
739
|
+
skip some of them and emit only the last one. That can be done with `throttleb`.
|
740
|
+
|
741
|
+
```ruby
|
742
|
+
trackable = Reacto.interval(1).take(30).throttle(5)
|
743
|
+
|
744
|
+
values = []
|
745
|
+
subscription = trackable.on(value: -> (val) { values << val })
|
746
|
+
trackable.await(subscription)
|
747
|
+
|
748
|
+
puts values.size # just 6
|
749
|
+
```
|
750
|
+
|
751
|
+
### Grouping
|
752
|
+
|
753
|
+
Incoming notifications can be grouped by some common property they have.
|
754
|
+
The resulting `Reacto::Trackable` emits special `LabeledTrackable` instances
|
755
|
+
which are just trackables with additional property - `label` - the name of
|
756
|
+
the group.
|
757
|
+
|
758
|
+
#### group_by_label
|
759
|
+
|
760
|
+
The most basic operation which groups values into sub-trackables is
|
761
|
+
`group_by_label` or just `group_by`:
|
762
|
+
|
763
|
+
```ruby
|
764
|
+
trackable = Reacto.enumerable((1..10)).group_by_label do |value|
|
765
|
+
[(value % 3), value]
|
766
|
+
end
|
767
|
+
|
768
|
+
trackable.on do |labeled_trackable|
|
769
|
+
p "Label: #{labeled_trackable.label}"
|
770
|
+
p "Values: #{labeled_trackable.to_a.join(',')}"
|
771
|
+
end
|
772
|
+
|
773
|
+
# This produces:
|
774
|
+
# Label: 1
|
775
|
+
# Values: 1,4,7,10
|
776
|
+
# Label: 2
|
777
|
+
# Values: 2,5,8
|
778
|
+
# Label: 0
|
779
|
+
# Values: 3,6,9
|
780
|
+
```
|
781
|
+
|
782
|
+
This example prints the label of every `Reacto::LabeledTrackable` emitted and
|
783
|
+
its values. It uses the `#to_a` method, which blocks and waits for every value
|
784
|
+
to be received, then produces an array with all the values in the order they
|
785
|
+
were received.
|
786
|
+
|
787
|
+
#### chunk
|
788
|
+
|
789
|
+
With `chunk` we can create `LabeledTrackable` instances emitting chunks based
|
790
|
+
on the return value of a block called on an emitted value. The difference with
|
791
|
+
`group_by` is that there can be multiple trackables with the same key.
|
792
|
+
|
793
|
+
```ruby
|
794
|
+
source = Reacto.enumerable([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5])
|
795
|
+
trackable = source.chunk { |val| val.even? }
|
796
|
+
|
797
|
+
trackable.on do |labeled_trackable|
|
798
|
+
p "Label: #{labeled_trackable.label}"
|
799
|
+
p "Values: #{labeled_trackable.to_a.join(',')}"
|
800
|
+
end
|
801
|
+
|
802
|
+
# This produces:
|
803
|
+
# Label: false
|
804
|
+
# Values: 3,1
|
805
|
+
# Label: true
|
806
|
+
# Values: 4
|
807
|
+
# Label: false
|
808
|
+
# Values: 1,5,9
|
809
|
+
# Label: true
|
810
|
+
# Values: 2,6
|
811
|
+
# Label: false
|
812
|
+
# Values: 5,3,5
|
813
|
+
```
|
814
|
+
|
815
|
+
The first chunk consists of `3` and `1` - odd values, then on the first even
|
816
|
+
value - `4` we get another chink, then we've got 3 sequential odd values, so
|
817
|
+
a `false` chunk of `1`, `5` and `9`, then one with `2` and `6` with label
|
818
|
+
`true`, because the values are even. The last chunk is an odd one.
|
819
|
+
|
820
|
+
#### operating only on given group
|
821
|
+
|
822
|
+
The `map` operator is able to operate only on a given group by passing it
|
823
|
+
a `label:` argument:
|
824
|
+
|
825
|
+
```ruby
|
826
|
+
source = Reacto.enumerable((1..10)).group_by_label do |value|
|
827
|
+
[(value % 3), value]
|
828
|
+
end
|
829
|
+
trackable = source.map(label: 0) { |value| value / 3 }
|
830
|
+
|
831
|
+
trackable.on do |labeled_trackable|
|
832
|
+
p "Label: #{labeled_trackable.label}"
|
833
|
+
p "Values: #{labeled_trackable.to_a.join(',')}"
|
834
|
+
end
|
835
|
+
|
836
|
+
# This produces:
|
837
|
+
# Label: 1
|
838
|
+
# Values: 1,4,7,10
|
839
|
+
# Label: 2
|
840
|
+
# Values: 2,5,8
|
841
|
+
# Label: 0
|
842
|
+
# Values: 1,2,3
|
843
|
+
```
|
844
|
+
|
845
|
+
As we can see only the values emitted by the `Trackable` with label `0`, the
|
846
|
+
ones that can be divided by 3 without remainder are affected by the `map`
|
847
|
+
operation. The operations `select`, `inject` and `flat_map` have a `label:`
|
848
|
+
argument too and can be applied only to sub-trackables with the passed label.
|
849
|
+
|
850
|
+
#### flatten_labeled
|
851
|
+
|
852
|
+
The `Reacto::LabeledTrackable` instances emitted by a `Trackable` after grouping
|
853
|
+
can be turned to simple value notifications by using the `flatten_labeled`
|
854
|
+
operation. It turns every sub-trackable into an object with two fields
|
855
|
+
label and value. The label is the same as the label of the sub-trackable the
|
856
|
+
object represents, and the value is accumulated with a block passed to
|
857
|
+
`flatten_labeled` from the notifications emitted by the sub-trackable.
|
858
|
+
It is the same as using inject:
|
859
|
+
|
860
|
+
```ruby
|
861
|
+
source = Reacto.enumerable((1..10)).group_by_label do |value|
|
862
|
+
[(value % 3), value]
|
863
|
+
end
|
864
|
+
trackable = source.flatten_labeled { |prev, curr| prev + curr }
|
865
|
+
|
866
|
+
trackable.on { |object| puts "#{object.label} : #{object.value}"}
|
867
|
+
|
868
|
+
# This produces:
|
869
|
+
# 1 : 22
|
870
|
+
# 2 : 15
|
871
|
+
# 0 : 18
|
872
|
+
```
|
873
|
+
|
874
|
+
Prints the original label and the sums of the values emitted by the original
|
875
|
+
`Reacto::LabeledTrackable`.
|
876
|
+
|
877
|
+
### Error handling
|
878
|
+
|
879
|
+
Sometimes we want to handle incoming error notification before actually going
|
880
|
+
out of the operation chain and in the error consumer code. This can be achieved
|
881
|
+
with some special operations, designed to work with errors.
|
882
|
+
|
883
|
+
#### retrying
|
884
|
+
|
885
|
+
The `retry` operator will execute the source's behavior when there is an error
|
886
|
+
notification instead of emitting it and closing the `Reacto::Trackable`.
|
887
|
+
|
888
|
+
```ruby
|
889
|
+
source = Reacto.make do |subscriber|
|
890
|
+
subscriber.on_value('Test your luck!')
|
891
|
+
number = Random.new.rand(1..10)
|
892
|
+
|
893
|
+
if number <= 5
|
894
|
+
subscriber.on_error(
|
895
|
+
StandardError.new("Bad luck, last number was : #{number}")
|
896
|
+
)
|
897
|
+
else
|
898
|
+
subscriber.on_value("Lucky number #{number}!")
|
899
|
+
subscriber.on_close
|
900
|
+
end
|
901
|
+
end
|
902
|
+
|
903
|
+
trackable = source.retry(5)
|
904
|
+
trackable.on(
|
905
|
+
value: -> (v) { puts v },
|
906
|
+
close: -> () { puts 'Done' },
|
907
|
+
error: -> (e) { puts e.message }
|
908
|
+
)
|
909
|
+
```
|
910
|
+
|
911
|
+
This piece of code will retry up to 5 times when the number is `5` or smaller.
|
912
|
+
On the sixth time if we don't have luck the error will be emitted. The default
|
913
|
+
retry count (when a value is not passed) is just `1`. There is a `retry_when`
|
914
|
+
operation, which uses a block to determine if the error should be emitted, or
|
915
|
+
the source should be retried. For example:
|
916
|
+
|
917
|
+
```ruby
|
918
|
+
source = Reacto.make do |subscriber|
|
919
|
+
subscriber.on_value('Test your luck!')
|
920
|
+
number = Random.new.rand(1..10)
|
921
|
+
|
922
|
+
if number <= 5
|
923
|
+
subscriber.on_error(
|
924
|
+
StandardError.new("Bad luck, last number was : #{number}")
|
925
|
+
)
|
926
|
+
else
|
927
|
+
subscriber.on_value("Lucky number #{number}!")
|
928
|
+
subscriber.on_close
|
929
|
+
end
|
930
|
+
end
|
931
|
+
|
932
|
+
trackable = source.retry_when do |error, retries|
|
933
|
+
retries < 5 && !error.message.include?('3')
|
934
|
+
end
|
935
|
+
|
936
|
+
trackable.on(
|
937
|
+
value: -> (v) { puts v },
|
938
|
+
close: -> () { puts 'Done' },
|
939
|
+
error: -> (e) { puts e.message }
|
940
|
+
)
|
941
|
+
```
|
942
|
+
|
943
|
+
In this example we use the block to say the that we retry at most 5 times
|
944
|
+
again, but this time if the unlucky number was `3` we should not retry.
|
945
|
+
|
946
|
+
#### how to rescue from errors
|
947
|
+
|
948
|
+
The simples way to not emit an error but to continue emitting something else
|
949
|
+
is by using the `rescue_and_replace_error_with` operation. This one accepts
|
950
|
+
a `Reacto::Trackable` instance as its sole argument.
|
951
|
+
When an error notification is emitted by its source `Trackable`, it is not
|
952
|
+
emitted by the trackable it returns. Instead the notifications of the argument
|
953
|
+
are emitted.
|
954
|
+
|
955
|
+
```ruby
|
956
|
+
source = Reacto.enumerable([1, 2, 3, 0, 7, 8, 9]).map do |val|
|
957
|
+
10 / val
|
958
|
+
end
|
959
|
+
|
960
|
+
trackable = source.rescue_and_replace_error_with(
|
961
|
+
Reacto::Trackable.enumerable((4..6)).map { |val| 10 / val }
|
962
|
+
)
|
963
|
+
|
964
|
+
trackable.on(
|
965
|
+
value: -> (v) { puts v },
|
966
|
+
close: -> () { puts 'Done' },
|
967
|
+
error: -> (e) { puts e.message }
|
968
|
+
)
|
969
|
+
```
|
970
|
+
|
971
|
+
We want see the error cause the by division by `0`, instead after the emission
|
972
|
+
of `10/1` -> `10`, `10/2` -> `5` and `10/3` -> `3`, the values `2`, `2` and `1`
|
973
|
+
will be emitted -> that's `10/4`, `10/5` and `10/6`.
|
974
|
+
|
975
|
+
Another more precise way to do that is to use the `rescue_and_replace_error`
|
976
|
+
operation which receives a block returning a `Reacto::Trackable` -
|
977
|
+
the replacement. The block has as an argument the original error, so some logic
|
978
|
+
can be written around that.
|
979
|
+
|
980
|
+
```ruby
|
981
|
+
source = Reacto.enumerable([1, 2, 3, 0, 7, 8, 9]).map do |val|
|
982
|
+
10 / val
|
983
|
+
end
|
984
|
+
|
985
|
+
trackable = source.rescue_and_replace_error do |error|
|
986
|
+
if error.is_a?(ArgumentError)
|
987
|
+
Reacto.error(error)
|
988
|
+
else
|
989
|
+
Reacto.value(1)
|
990
|
+
end
|
991
|
+
end
|
992
|
+
|
993
|
+
trackable.on(
|
994
|
+
value: -> (v) { puts v },
|
995
|
+
close: -> () { puts 'Done' },
|
996
|
+
error: -> (e) { puts e.message }
|
997
|
+
)
|
998
|
+
```
|
999
|
+
|
1000
|
+
The number `1` will be emitted instead of the `ZeroDivisionError` because it is
|
1001
|
+
not an `ArgumentError`.
|
1002
|
+
|
1003
|
+
## Dependencies
|
1004
|
+
|
1005
|
+
Reacto is powered by [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby)
|
1006
|
+
|
1007
|
+
## Tested with
|
89
1008
|
|
1009
|
+
* Ruby 2.0.0+
|
1010
|
+
* JRuby 9.1.2.0
|