Skip to content

Commit c205cbc

Browse files
authored
Merge pull request #2 from AsyncCommunity/feature/README
Feature/readme
2 parents 4fde542 + 145bf60 commit c205cbc

13 files changed

Lines changed: 452 additions & 71 deletions

README.md

Lines changed: 379 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,381 @@
11
# AsyncExtensions
22

3-
AsyncExtensions mimics combine operators for async sequences.
3+
4+
<p align="left">
5+
<img src="https://github.com/AsyncCommunity/AsyncExtensions/actions/workflows/ci.yml/badge.svg?branch=main" alt="Build Status" title="Build Status">
6+
<a href="https://codecov.io/gh/AsyncCommunity/AsyncExtensions"><img src="https://codecov.io/gh/AsyncCommunity/AsyncExtensions/branch/main/graph/badge.svg?token=NTGOIK6CSE"/></a>
7+
<a href="https://github.com/apple/swift-package-manager" target="_blank"><img src="https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg" alt="CombineExt supports Swift Package Manager (SPM)"></a>
8+
<img src="https://img.shields.io/badge/platforms-iOS%2013%20%7C%20macOS 10.15%20%7C%20tvOS%2013%20%7C%20watchOS%206-333333.svg" />
9+
10+
AsyncExtensions provides a collection of operators, async sequences and async streams that mimics Combine behaviour.
11+
12+
The purpose is to be able to chain operators, just as you would do with any reactive programming framework:
13+
14+
```swift
15+
AsyncSequences
16+
.Merge(sequence1, sequence2, sequence3)
17+
.prepend(0)
18+
.handleEvents(onElement: { print($0) }, onFinish: { print("Finished") })
19+
.scan("") { accumulator, element in accumulator + "\(element)" }
20+
.collect { print($0) }
21+
```
22+
23+
### Async Sequences
24+
* [Just](#Just)
25+
* [Empty](#Empty)
26+
* [Fail](#Fail)
27+
* [From](#From)
28+
* [Merge](#Merge)
29+
* [Zip2](#Zip2)
30+
* [Zip3](#Zip3)
31+
* [Zip](#Zip)
32+
33+
### Async Streams
34+
* [Passthrough](#Passthrough)
35+
* [CurrentValue](#CurrentValue)
36+
* [Replay](#Replay)
37+
38+
### Operators
39+
* [Collect](#Collect)
40+
* [Scan](#Scan)
41+
* [SwitchToLatest](#SwitchToLatest)
42+
* [FlatMapLatest](#FlatMapLatest)
43+
* [HandleEvents](#HandleEvents)
44+
* [EraseToAnyAsyncSequence](#EraseToAnyAsyncSequence)
45+
46+
More operators and extensions are to come. Pull requests are of course welcome.
47+
48+
## Async Sequences
49+
50+
### Just
51+
52+
`Just` is an AsyncSequence that outputs a single value and finishes.
53+
54+
```swift
55+
let justSequence = AsyncSequences.Just<Int>(1)
56+
for try await element in justSequence {
57+
// will be called once with element = 1
58+
}
59+
```
60+
61+
### Empty
62+
63+
`Empty` is an AsyncSequence that immediately finishes without emitting values.
64+
65+
```swift
66+
let emptySequence = AsyncSequences.Empty<Int>()
67+
for try await element in emptySequence {
68+
// will never be called
69+
}
70+
```
71+
72+
### Fail
73+
74+
`Fail` is an AsyncSequence that outputs no elements and throws an error.
75+
76+
```swift
77+
let failSequence = AsyncSequences.Fail<Int, Swift.Error>(error: NSError(domain: "", code: 1))
78+
do {
79+
for try await element in failSequence {
80+
// will never be called
81+
}
82+
} catch {
83+
// will catch `NSError(domain: "", code: 1)` here
84+
}
85+
```
86+
87+
### From
88+
89+
`From` is an AsyncSequence that outputs elements from a traditional Sequence.
90+
91+
```swift
92+
let fromSequence = AsyncSequences.From([1, 2, 3, 4, 5])
93+
94+
for await element in fromSequence {
95+
print(element) // will print 1 2 3 4 5
96+
}
97+
```
98+
99+
A variation offers to set an interval of time between each element.
100+
101+
```swift
102+
let fromSequence = AsyncSequences.From([1, 2, 3, 4, 5], interval: .milliSeconds(10))
103+
104+
for await element in fromSequence {
105+
print(element) // will print 1 2 3 4 5 with an interval of 10ms between elements
106+
}
107+
```
108+
109+
### Merge
110+
111+
`Merge` is an AsyncSequence that merges several async sequences respecting their temporality while being iterated over.
112+
When all the async sequences have finished, so too does the merged async sequence.
113+
If an async sequence fails, so too does the merged async sequence.
114+
115+
```swift
116+
// 0.1ms 1ms 1.5ms 2ms 3ms 4.5ms
117+
// 4 1 5 2 3 6
118+
119+
let asyncSequence1 = AsyncStream(Int.self, bufferingPolicy: .unbounded) { continuation in
120+
Task {
121+
try await Task.sleep(nanoseconds: 1_000_000)
122+
continuation.yield(1)
123+
try await Task.sleep(nanoseconds: 1_000_000)
124+
continuation.yield(2)
125+
try await Task.sleep(nanoseconds: 1_000_000)
126+
continuation.yield(3)
127+
continuation.finish()
128+
}
129+
}
130+
131+
let asyncSequence2 = AsyncStream(Int.self, bufferingPolicy: .unbounded) { continuation in
132+
Task {
133+
try await Task.sleep(nanoseconds: 100_000)
134+
continuation.yield(4)
135+
try await Task.sleep(nanoseconds: 1_400_000)
136+
continuation.yield(5)
137+
try await Task.sleep(nanoseconds: 3_000_000)
138+
continuation.yield(6)
139+
continuation.finish()
140+
}
141+
}
142+
143+
let mergedAsyncSequence = AsyncSequences.Merge(asyncSequence1, asyncSequence2)
144+
145+
for try await element in mergedAsyncSequence {
146+
print(element) // will print -> 4 1 5 2 3 6
147+
}
148+
```
149+
150+
### Zip2
151+
152+
`Zip2` is an AsyncSequence that combines the latest elements from two sequences according to their temporality and emits a tuple to the client.
153+
If any async sequence ends successfully or fails with an error, so too does the zipped async Sequence.
154+
155+
```swift
156+
let asyncSequence1 = AsyncSequences.From([1, 2, 3, 4, 5])
157+
let asyncSequence2 = AsyncSequences.From(["5", "4", "3", "2", "1"])
158+
159+
let zippedAsyncSequence = AsyncSequences.Zip2(asyncSequence1, asyncSequence2)
160+
161+
for try await element in zippedAsyncSequence {
162+
print(element) // will print -> (1, "5") (2, "4") (3, "3") (4, "2") (5, "1")
163+
}
164+
```
165+
166+
### Zip3
167+
168+
`Zip3` is an AsyncSequence that combines the latest elements from two sequences according to their temporality and emits a tuple to the client.
169+
If any async sequence ends successfully or fails with an error, so too does the zipped async Sequence.
170+
171+
```swift
172+
let asyncSequence1 = AsyncSequences.From([1, 2, 3, 4, 5])
173+
let asyncSequence2 = AsyncSequences.From(["5", "4", "3", "2", "1"])
174+
let asyncSequence3 = AsyncSequences.From([true, false, true, false, true])
175+
176+
let zippedAsyncSequence = AsyncSequences.Zip3(asyncSequence1, asyncSequence2, asyncSequence3)
177+
178+
for try await element in zippedAsyncSequence {
179+
print(element) // will print -> (1, "5", true) (2, "4", false) (3, "3", true) (4, "2", false) (5, "1", true)
180+
}
181+
```
182+
183+
### Zip
184+
185+
`Zip` is an AsyncSequence that combines the latest elements from several sequences according to their temporality and emits an array to the client.
186+
If any async sequence ends successfully or fails with an error, so too does the zipped async Sequence.
187+
188+
```swift
189+
let asyncSequence1 = AsyncSequences.From([1, 2, 3])
190+
let asyncSequence2 = AsyncSequences.From([1, 2, 3])
191+
let asyncSequence3 = AsyncSequences.From([1, 2, 3])
192+
let asyncSequence4 = AsyncSequences.From([1, 2, 3])
193+
let asyncSequence5 = AsyncSequences.From([1, 2, 3])
194+
195+
let zippedAsyncSequence = AsyncSequences.Zip(asyncSequence1, asyncSequence2, asyncSequence3, asyncSequence4, asyncSequence5)
196+
197+
for try await element in zippedAsyncSequence {
198+
print(element) // will print -> [1, 1, 1, 1, 1] [2, 2, 2, 2, 2] [3, 3, 3, 3, 3]
199+
}
200+
```
201+
202+
## Async Streams
203+
204+
### Passthrough
205+
206+
`Passthrough` is an async sequence in which one can send values over time.
207+
208+
```swift
209+
let passthrough = AsyncStreams.Passthrough<Int>()
210+
211+
Task {
212+
for try await element in passthrough {
213+
print(element) // will print 1 2
214+
}
215+
}
216+
217+
Task {
218+
for try await element in passthrough {
219+
print(element) // will print 1 2
220+
}
221+
}
222+
223+
.. later in the application flow
224+
225+
passthrough.send(1)
226+
passthrough.send(2)
227+
```
228+
229+
### CurrentValue
230+
231+
`CurrentValue` is an async sequence in which one can send values over time.
232+
The current value is always accessible as an instance variable.
233+
The current value is replayed for any new async loop.
234+
235+
```swift
236+
let currentValue = AsyncStreams.CurrentValue<Int>(1)
237+
238+
Task {
239+
for try await element in passthrough {
240+
print(element) // will print 1 2
241+
}
242+
}
243+
244+
Task {
245+
for try await element in passthrough {
246+
print(element) // will print 1 2
247+
}
248+
}
249+
250+
.. later in the application flow
251+
252+
currentValue.send(2)
253+
254+
print(currentValue.element) // will print 2
255+
```
256+
257+
### Replay
258+
259+
`Replay`is an async sequence in which one can send values over time.
260+
Values are buffered in a FIFO fashion so they can be iterated over by new loops.
261+
When the `bufferSize` is outreached the oldest value is dropped.
262+
263+
```swift
264+
let replay = AsyncStreams.Replay<Int>(bufferSize: 3)
265+
266+
(1...5).forEach { replay.send($0) }
267+
268+
for try await element in replay {
269+
print(element) // will print 3, 4, 5
270+
}
271+
```
272+
273+
## Operators
274+
275+
### Collect
276+
277+
`collect(_:)` iterates over each element of the AsyncSequence and give it to the async block.
278+
279+
```swift
280+
let fromSequence = AsyncSequences.From([1, 2, 3])
281+
fromSequence
282+
.collect { print($0) } // will print 1 2 3
283+
```
284+
285+
### Scan
286+
287+
`scan(_:_:)` transforms elements from the upstream async sequence by providing the current element to a closure along with the last value returned by the closure. Each intermediate value will be emitted in the downstream async sequence.
288+
289+
```swift
290+
let sourceSequence = AsyncSequences.From([1, 2, 3, 4, 5])
291+
let scannedSequence = sourceSequence.scan("") { accumulator, element in accumulator + "\(element)"}
292+
293+
for try await element in scannedSequence {
294+
print(element)
295+
}
296+
297+
// will print:
298+
1
299+
12
300+
123
301+
1234
302+
12345
303+
```
304+
305+
### SwitchToLatest
306+
307+
`switchToLatest()` re-emits elements sent by the most recently received async sequence. This operator applies only in the case where the upstream async sequence's `Element` is it-self an async sequence.
308+
309+
```
310+
let sourceSequence = AsyncSequences.From([1, 2, 3])
311+
let mappedSequence = sourceSequence.map { element in
312+
AsyncSequences.From(["a\(element)", "b\(element)"])
313+
}
314+
let switchedSequence = mappedSequence.switchToLatest()
315+
316+
for try await element in switchedSequence {
317+
print(element) // will print a3 b3
318+
}
319+
```
320+
321+
### FlatMapLatest
322+
323+
`flatMapLatest(_:)` transforms the upstream async sequence elements into an async sequence and flattens the sequence of events from these multiple sources async sequences to appear as if they were coming from a single async sequence of events. Mapping to a new async sequence will cancel the task related to the previous one.
324+
325+
This operator is basically a shortcut for `map()` and `switchToLatest()`.
326+
327+
```swift
328+
let sourceSequence = AsyncSequences.From([1, 2, 3])
329+
let flatMapLatestSequence = sourceSequence.flatMapLatest { element in
330+
AsyncSequences.From(["a\(element)", "b\(element)"])
331+
}
332+
333+
for try await element in flatMapLatestSequence {
334+
print(element) // will print a3 b3
335+
}
336+
```
337+
338+
### Prepend
339+
340+
`prepend(_:)` prepends an element to the upstream async sequence.
341+
342+
```swift
343+
let sourceSequence = AsyncSequences.From([1, 2, 3])
344+
let prependSequence = sourceSequence.prepend(0)
345+
346+
for try await element in prependSequence {
347+
print(element) // will print 0 1 2 3
348+
}
349+
```
350+
351+
### HandleEvents
352+
353+
`handleEvents(onStart:onElement:onCancel:onFinish)` performs the specified closures when async sequences events occur.
354+
355+
```swift
356+
let sourceSequence = AsyncSequences.From([1, 2, 3, 4, 5])
357+
let handledSequence = sourceSequence.handleEvents {
358+
print("Begin iterating")
359+
} onElement: { element in
360+
print("Element is \(element)")
361+
} onCancel: {
362+
print("Cancelled")
363+
} onFinish: { termination in
364+
print(termination)
365+
}
366+
367+
for try await element in handledSequence {}
368+
369+
// will print:
370+
// Begin iterating
371+
// Element is 1
372+
// Element is 2
373+
// Element is 3
374+
// Element is 4
375+
// Element is 5
376+
// finished
377+
```
378+
379+
### EraseToAnyAsyncSequence
380+
381+
`eraseToAnyAsyncSequence()` type-erases the async sequence into an AnyAsyncSequence.

0 commit comments

Comments
 (0)