├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ └── ci.yml
├── .gitignore
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ └── AsyncExtensions.xcscheme
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE
├── PULL_REQUEST_TEMPLATE.md
├── Package.resolved
├── Package.swift
├── README.md
├── Sources
├── AsyncChannels
│ ├── AsyncBufferedChannel.swift
│ └── AsyncThrowingBufferedChannel.swift
├── AsyncSubjects
│ ├── AsyncCurrentValueSubject.swift
│ ├── AsyncPassthroughSubject.swift
│ ├── AsyncReplaySubject.swift
│ ├── AsyncSubject.swift
│ ├── AsyncThrowingCurrentValueSubject.swift
│ ├── AsyncThrowingPassthroughSubject.swift
│ ├── AsyncThrowingReplaySubject.swift
│ └── Streamed.swift
├── Combiners
│ ├── Merge
│ │ ├── AsyncMerge2Sequence.swift
│ │ ├── AsyncMerge3Sequence.swift
│ │ ├── AsyncMergeSequence.swift
│ │ └── MergeStateMachine.swift
│ ├── WithLatestFrom
│ │ ├── AsyncWithLatestFrom2Sequence.swift
│ │ └── AsyncWithLatestFromSequence.swift
│ └── Zip
│ │ ├── AsyncZip2Sequence.swift
│ │ ├── AsyncZip3Sequence.swift
│ │ ├── AsyncZipSequence.swift
│ │ ├── Zip2Runtime.swift
│ │ ├── Zip2StateMachine.swift
│ │ ├── Zip3Runtime.swift
│ │ ├── Zip3StateMachine.swift
│ │ ├── ZipRuntime.swift
│ │ └── ZipStateMachine.swift
├── Common
│ └── Termination.swift
├── Creators
│ ├── AsyncEmptySequence.swift
│ ├── AsyncFailSequence.swift
│ ├── AsyncJustSequence.swift
│ ├── AsyncLazySequence.swift
│ ├── AsyncStream+Pipe.swift
│ ├── AsyncThrowingJustSequence.swift
│ └── AsyncTimerSequence.swift
├── Operators
│ ├── AsyncHandleEventsSequence.swift
│ ├── AsyncMapToResultSequence.swift
│ ├── AsyncMulticastSequence.swift
│ ├── AsyncPrependSequence.swift
│ ├── AsyncScanSequence.swift
│ ├── AsyncSequence+Assign.swift
│ ├── AsyncSequence+Collect.swift
│ ├── AsyncSequence+EraseToAnyAsyncSequence.swift
│ ├── AsyncSequence+FlatMapLatest.swift
│ ├── AsyncSequence+Share.swift
│ └── AsyncSwitchToLatestSequence.swift
└── Supporting
│ ├── ManagedCriticalState.swift
│ ├── Regulator.swift
│ └── Result+ErrorMechanism.swift
└── Tests
├── AsyncChannels
├── AsyncBufferedChannelTests.swift
└── AsyncBufferedThrowingChannelTests.swift
├── AsyncSubjets
├── AsyncCurrentValueSubjectTests.swift
├── AsyncPassthroughSubjectTests.swift
├── AsyncReplaySubjectTests.swift
├── AsyncThrowingCurrentValueSubjectTests.swift
├── AsyncThrowingPassthroughSubjectTests.swift
├── AsyncThrowingReplaySubjectTests.swift
└── StreamedTests.swift
├── Combiners
├── Merge
│ └── AsyncMergeSequenceTests.swift
├── WithLatestFrom
│ ├── AsyncWithLatestFrom2SequenceTests.swift
│ └── AsyncWithLatestFromSequenceTests.swift
└── Zip
│ └── AsyncZipSequenceTests.swift
├── Creators
├── AsyncEmptySequenceTests.swift
├── AsyncFailSequenceTests.swift
├── AsyncJustSequenceTests.swift
├── AsyncLazySequenceTests.swift
├── AsyncStream+PipeTests.swift
├── AsyncThrowingJustSequenceTests.swift
└── AsyncTimerSequenceTests.swift
├── Operators
├── AsyncHandleEventsSequenceTests.swift
├── AsyncMapToResultSequenceTests.swift
├── AsyncMulticastSequenceTests.swift
├── AsyncPrependSequenceTests.swift
├── AsyncScanSequenceTests.swift
├── AsyncSequence+AssignTests.swift
├── AsyncSequence+CollectTests.swift
├── AsyncSequence+EraseToAnyAsyncSequenceTests.swift
├── AsyncSequence+FlatMapLatestTests.swift
├── AsyncSequence+ShareTests.swift
└── AsyncSwitchToLatestSequenceTests.swift
└── Supporting
└── Helpers.swift
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]"
5 | labels: bug
6 | assignees: twittemb
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | Provide code snippets, or links to gist or repos if possible.
21 |
22 | **Expected behavior**
23 | A clear and concise description of what you expected to happen.
24 |
25 | **Screenshots**
26 | If applicable, add screenshots to help explain your problem.
27 |
28 | **Environment:**
29 | - Version of the library
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: "[FEATURE REQUEST]"
5 | labels: enhancement
6 | assignees: twittemb
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build and test
2 |
3 | on: [push]
4 |
5 | jobs:
6 | Build:
7 |
8 | runs-on: macos-12
9 |
10 | steps:
11 | - name: Checkout branch
12 | uses: actions/checkout@v3
13 | - name: Build
14 | run: swift build -Xswiftc -suppress-warnings
15 |
16 | Test:
17 |
18 | runs-on: macos-12
19 |
20 | steps:
21 | - name: Checkout branch
22 | uses: actions/checkout@v3
23 | - name: Test
24 | run: swift test --enable-code-coverage -Xswiftc -suppress-warnings
25 | - name: Generate coverage
26 | uses: sersoft-gmbh/swift-coverage-action@v3
27 | id: coverage-files
28 | - name: Upload coverage
29 | uses: codecov/codecov-action@v3
30 | with:
31 | files: ${{join(fromJSON(steps.coverage-files.outputs.files), ',')}}
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata/
6 | DerivedData/
7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/AsyncExtensions.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
52 |
53 |
54 |
55 |
57 |
63 |
64 |
65 |
66 |
67 |
77 |
78 |
84 |
85 |
91 |
92 |
93 |
94 |
96 |
97 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | **v0.5.2 - Oxygen:**
2 |
3 | This version is a bug fix version.
4 |
5 | - Multicast: don't cancel the upstream sequence when a client is cancelled (https://github.com/sideeffect-io/AsyncExtensions/pull/32)
6 | - SwitchToLatest: fix a situation where child task could hang indefinitely (https://github.com/sideeffect-io/AsyncExtensions/pull/20)
7 |
8 | **v0.5.1 - Nitrogen:**
9 |
10 | This version removes compilation unsafe flags
11 |
12 | **v0.5.0 - Carbon:**
13 |
14 | This version brings a lot of internal refactoring and breaking changes + some new operators.
15 |
16 | Now `swift-async-algorithms` has been anounced, this library can be seen as a companion for the Apple repo.
17 | For now there is an overlap between both libraries, but when `swift-async-algorithms` becomes stable the overlapping operators while be deprecated in `AsyncExtensions`.
18 |
19 | Nevertheless `AsyncExtensions` will continue to provide the operators that the community needs and are not provided by Apple.
20 |
21 | - `AsyncBufferedChannel`/`AsyncThrowingBufferedChannel`: is the equivalent to `AsyncChannel` from Apple. The difference is that back-pressure is handled with a stack and the send operation is not suspending.
22 | - Subjects: the `subject` suffix has been adopted to all the "hot" `AsyncSequence` with a shared output. A throwing counterpart has been added.
23 | - `zip` and `merge` are top level functions to match Apple repo.
24 | - `AsyncThrowingJustSequence`: an `AsyncSequence` that takes a throwing closure to compute the only element to emit.
25 | - `AsyncStream.pipe()`: creates and `AsyncStream` by escaping the `Continuation` and returning a tuple to manipulate the inputs and outputs of the stream.
26 | - `mapToResult()`: maps events (elements or failure) from an `AsyncSequence` to a `Result`. The resulting `AsyncSequence` cannot fail.
27 | - `AsyncLazySequence`: is a renaming to match Apple repo for creating an `AsyncSequence` from a `Sequence`.
28 |
29 | **v0.4.0 - Bore:**
30 |
31 | - AsyncStreams: new @Streamed property wrapper
32 | - AsyncSequences: finish Timer when canceled
33 |
34 | **v0.3.0 - Beryllium:**
35 |
36 | - Operators: new Share operator
37 | - AsyncSequences: new Timer AsyncSequence
38 | - AsyncStreams: `send()` functions are now synchronous
39 |
40 | **v0.2.1 - Lithium:**
41 |
42 | - Enforce the call of onNext in a determinitisc way
43 |
44 | **v0.2.0 - Helium:**
45 |
46 | - AsyncStreams.CurrentValue `element` made public and available with get/set
47 | - new Multicast operator
48 | - new Assign operator
49 |
50 | **v0.1.0 - Hydrogen:**
51 |
52 | - Implements the first set of extensions
53 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Code of Conduct
2 |
3 | As contributors and maintainers of this project, and in the interest of
4 | fostering an open and welcoming community, we pledge to respect all people who
5 | contribute through reporting issues, posting feature requests, updating
6 | documentation, submitting pull requests or patches, and other activities.
7 |
8 | We are committed to making participation in this project a harassment-free
9 | experience for everyone, regardless of level of experience, gender, gender
10 | identity and expression, sexual orientation, disability, personal appearance,
11 | body size, race, ethnicity, age, religion, or nationality.
12 |
13 | Examples of unacceptable behavior by participants include:
14 |
15 | * The use of sexualized language or imagery
16 | * Personal attacks
17 | * Trolling or insulting/derogatory comments
18 | * Public or private harassment
19 | * Publishing other's private information, such as physical or electronic addresses,
20 | without explicit permission
21 | * Other unethical or unprofessional conduct
22 |
23 | Project maintainers have the right and responsibility to remove, edit, or
24 | reject comments, commits, code, wiki edits, issues, and other contributions
25 | that are not aligned to this Code of Conduct, or to ban temporarily or
26 | permanently any contributor for other behaviors that they deem inappropriate,
27 | threatening, offensive, or harmful.
28 |
29 | By adopting this Code of Conduct, project maintainers commit themselves to
30 | fairly and consistently applying these principles to every aspect of managing
31 | this project. Project maintainers who do not follow or enforce the Code of
32 | Conduct may be permanently removed from the project team.
33 |
34 | This code of conduct applies both within project spaces and in public spaces
35 | when an individual is representing the project or its community.
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
38 | reported by contacting a project maintainer at **thibault.wittemberg@gmail.com**. All
39 | complaints will be reviewed and investigated and will result in a response that
40 | is deemed necessary and appropriate to the circumstances. Maintainers are
41 | obligated to maintain confidentiality with regard to the reporter of an
42 | incident.
43 |
44 | This Code of Conduct is adapted from the Contributor Covenant,
45 | version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/](http://contributor-covenant.org/version/1/3/0/)
46 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 AsyncCommunity
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
4 | ## Checklist
5 |
6 | - [ ] this PR is based on the **main** branch and is up-to-date, if not please rebase your branch on the top of **main**
7 | - [ ] the commits inside this PR have explicit commit messages
8 | - [ ] unit tests cover the new feature or the bug fix
9 | - [ ] the feature is documented in the README.md if it makes sense
10 | - [ ] the CHANGELOG is up-to-date
11 |
--------------------------------------------------------------------------------
/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "swift-collections",
6 | "repositoryURL": "https://github.com/apple/swift-collections.git",
7 | "state": {
8 | "branch": null,
9 | "revision": "f504716c27d2e5d4144fa4794b12129301d17729",
10 | "version": "1.0.3"
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "AsyncExtensions",
8 | platforms: [
9 | .iOS(.v13),
10 | .macOS(.v10_15),
11 | .tvOS(.v13),
12 | .watchOS(.v6)
13 | ],
14 | products: [
15 | .library(
16 | name: "AsyncExtensions",
17 | targets: ["AsyncExtensions"]),
18 | ],
19 | dependencies: [.package(url: "https://github.com/apple/swift-collections.git", .upToNextMajor(from: "1.0.3"))],
20 | targets: [
21 | .target(
22 | name: "AsyncExtensions",
23 | dependencies: [.product(name: "Collections", package: "swift-collections")],
24 | path: "Sources"
25 | // ,
26 | // swiftSettings: [
27 | // .unsafeFlags([
28 | // "-Xfrontend", "-warn-concurrency",
29 | // "-Xfrontend", "-enable-actor-data-race-checks",
30 | // ])
31 | // ]
32 | ),
33 | .testTarget(
34 | name: "AsyncExtensionsTests",
35 | dependencies: ["AsyncExtensions"],
36 | path: "Tests"),
37 | ]
38 | )
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AsyncExtensions
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | **AsyncExtensions** provides a collection of operators that intends to ease the creation and combination of `AsyncSequences`.
11 |
12 | **AsyncExtensions** can be seen as a companion to Apple [swift-async-algorithms](https://github.com/apple/swift-async-algorithms). For now there is an overlap between both libraries, but when **swift-async-algorithms** becomes stable the overlapping operators while be deprecated in **AsyncExtensions**. Nevertheless **AsyncExtensions** will continue to provide the operators that the community needs and are not provided by Apple.
13 |
14 | ## Adding AsyncExtensions as a Dependency
15 |
16 | To use the `AsyncExtensions` library in a SwiftPM project,
17 | add the following line to the dependencies in your `Package.swift` file:
18 |
19 | ```swift
20 | .package(url: "https://github.com/sideeffect-io/AsyncExtensions"),
21 | ```
22 |
23 | Include `"AsyncExtensions"` as a dependency for your executable target:
24 |
25 | ```swift
26 | .target(name: "", dependencies: ["AsyncExtensions"]),
27 | ```
28 |
29 | Finally, add `import AsyncExtensions` to your source code.
30 |
31 | ## Features
32 |
33 | ### Channels
34 | * [AsyncBufferedChannel](./Sources/AsyncChannels/AsyncBufferedChannel.swift): Buffered communication channel between tasks. The elements are not shared and will be spread across consumers (same as
35 | AsyncStream)
36 | * [AsyncThrowingBufferedChannel](./Sources/AsyncChannels/AsyncThrowingBufferedChannel.swift): Throwing buffered communication channel between tasks
37 |
38 | ### Subjects
39 | * [AsyncPassthroughSubject](./Sources/AsyncSubjects/AsyncPassthroughSubject.swift): Subject with a shared output
40 | * [AsyncThrowingPassthroughSubject](./Sources/AsyncSubjects/AsyncThrowingPassthroughSubject.swift): Throwing subject with a shared output
41 | * [AsyncCurrentValueSubject](./Sources/AsyncSubjects/AsyncCurrentValueSubject.swift): Subject with a shared output. Maintain an replays its current value
42 | * [AsyncThrowingCurrentValueSubject](./Sources/AsyncSubjects/AsyncThrowingCurrentValueSubject.swift): Throwing subject with a shared output. Maintain an replays its current value
43 | * [AsyncReplaySubject](./Sources/AsyncSubjects/AsyncReplaySubject.swift): Subject with a shared output. Maintain an replays a buffered amount of values
44 | * [AsyncThrowingReplaySubject](./Sources/AsyncSubjects/AsyncThrowingReplaySubject.swift): Throwing subject with a shared output. Maintain an replays a buffered amount of values
45 |
46 | ### Combiners
47 | * [`zip(_:_:)`](./Sources/Combiners/Zip/AsyncZip2Sequence.swift): Zips two `AsyncSequence` into an AsyncSequence of tuple of elements
48 | * [`zip(_:_:_:)`](./Sources/Combiners/Zip/AsyncZip3Sequence.swift): Zips three `AsyncSequence` into an AsyncSequence of tuple of elements
49 | * [`zip(_:)`](./Sources/Combiners/Zip/AsyncZipSequence.swift): Zips any async sequences into an array of elements
50 | * [`merge(_:_:)`](./Sources/Combiners/Merge/AsyncMerge2Sequence.swift): Merges two `AsyncSequence` into an AsyncSequence of elements
51 | * [`merge(_:_:_:)`](./Sources/Combiners/Merge/AsyncMerge3Sequence.swift): Merges three `AsyncSequence` into an AsyncSequence of elements
52 | * [`merge(_:)`](./Sources/Combiners/Merge/AsyncMergeSequence.swift): Merges any `AsyncSequence` into an AsyncSequence of elements
53 | * [`withLatest(_:)`](./Sources/Combiners/WithLatestFrom/AsyncWithLatestFromSequence.swift): Combines elements from self with the last known element from an other `AsyncSequence`
54 | * [`withLatest(_:_:)`](./Sources/Combiners/WithLatestFrom/AsyncWithLatestFrom2Sequence.swift): Combines elements from self with the last known elements from two other async sequences
55 |
56 | ### Creators
57 | * [AsyncEmptySequence](./Sources/Creators/AsyncEmptySequence.swift): Creates an `AsyncSequence` that immediately finishes
58 | * [AsyncFailSequence](./Sources/Creators/AsyncFailSequence.swift): Creates an `AsyncSequence` that immediately fails
59 | * [AsyncJustSequence](./Sources/Creators/AsyncJustSequence.swift): Creates an `AsyncSequence` that emits an element an finishes
60 | * [AsyncThrowingJustSequence](./Sources/Creators/AsyncThrowingJustSequence.swift): Creates an `AsyncSequence` that emits an elements and finishes bases on a throwing closure
61 | * [AsyncLazySequence](./Sources/Creators/AsyncLazySequence.swift): Creates an `AsyncSequence` of the elements from the base sequence
62 | * [AsyncTimerSequence](./Sources/Creators/AsyncTimerSequence.swift): Creates an `AsyncSequence` that emits a date value periodically
63 | * [AsyncStream Pipe](./Sources/Creators/AsyncStream+Pipe.swift): Creates an AsyncStream and returns a tuple standing for its inputs and outputs
64 |
65 | ### Operators
66 | * [`handleEvents()`](./Sources/Operators/AsyncHandleEventsSequence.swift): Executes closures during the lifecycle of the self
67 | * [`mapToResult()`](./Sources/Operators/AsyncMapToResultSequence.swift): Maps elements and failure from self to a `Result` type
68 | * [`prepend(_:)`](./Sources/Operators/AsyncPrependSequence.swift): Prepends an element to self
69 | * [`scan(_:_:)`](./Sources/Operators/AsyncScanSequence.swift): Transforms elements from self by providing the current element to a closure along with the last value returned by the closure
70 | * [`assign(_:)`](./Sources/Operators/AsyncSequence+Assign.swift): Assigns elements from self to a property
71 | * [`collect(_:)`](./Sources/Operators/AsyncSequence+Collect.swift): Iterate over elements from self and execute a closure
72 | * [`eraseToAnyAsyncSequence()`](./Sources/Operators/AsyncSequence+EraseToAnyAsyncSequence.swift): Erases to AnyAsyncSequence
73 | * [`flatMapLatest(_:)`](./Sources/Operators/AsyncSequence+FlatMapLatest.swift): Transforms elements from self into a `AsyncSequence` and republishes elements sent by the most recently received `AsyncSequence` when self is an `AsyncSequence` of `AsyncSequence`
74 | * [`multicast(_:)`](./Sources/Operators/AsyncMulticastSequence.swift): Shares values from self to several consumers thanks to a provided Subject
75 | * [`share()`](./Sources/Operators/AsyncSequence+Share.swift): Shares values from self to several consumers
76 | * [`switchToLatest()`](./Sources/Operators/AsyncSwitchToLatestSequence.swift): Republishes elements sent by the most recently received `AsyncSequence` when self is an `AsyncSequence` of `AsyncSequence`
77 |
78 | More operators and extensions are to come. Pull requests are of course welcome.
79 |
--------------------------------------------------------------------------------
/Sources/AsyncChannels/AsyncBufferedChannel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncBufferedChannel.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 07/01/2022.
6 | //
7 |
8 | import DequeModule
9 | import OrderedCollections
10 |
11 | /// A channel for sending elements from one task to another.
12 | ///
13 | /// The `AsyncBufferedChannel` class is intended to be used as a communication type between tasks,
14 | /// particularly when one task produces values and another task consumes those values. The values are
15 | /// buffered awaiting a consumer to consume them from iteration.
16 | /// `finish()` induces a terminal state and no further elements can be sent.
17 | ///
18 | /// ```swift
19 | /// let channel = AsyncBufferedChannel()
20 | ///
21 | /// Task {
22 | /// for await element in channel {
23 | /// print(element) // will print 1, 2, 3
24 | /// }
25 | /// }
26 | ///
27 | /// sut.send(1)
28 | /// sut.send(2)
29 | /// sut.send(3)
30 | /// sut.finish()
31 | /// ```
32 | public final class AsyncBufferedChannel: AsyncSequence, Sendable {
33 | public typealias Element = Element
34 | public typealias AsyncIterator = Iterator
35 |
36 | struct Awaiting: Hashable {
37 | let id: Int
38 | let continuation: UnsafeContinuation?
39 |
40 | static func placeHolder(id: Int) -> Awaiting {
41 | Awaiting(id: id, continuation: nil)
42 | }
43 |
44 | func hash(into hasher: inout Hasher) {
45 | hasher.combine(self.id)
46 | }
47 |
48 | static func == (lhs: Awaiting, rhs: Awaiting) -> Bool {
49 | lhs.id == rhs.id
50 | }
51 | }
52 |
53 | enum SendDecision {
54 | case resume(Awaiting, Element)
55 | case terminate([Awaiting])
56 | case nothing
57 | }
58 |
59 | enum AwaitingDecision {
60 | case resume(Element?)
61 | case suspend
62 | }
63 |
64 | enum Value {
65 | case element(Element)
66 | case termination
67 | }
68 |
69 | enum State: @unchecked Sendable {
70 | case idle
71 | case queued(Deque)
72 | case awaiting(OrderedSet)
73 | case finished
74 |
75 | static var initial: State {
76 | .idle
77 | }
78 | }
79 |
80 | let ids: ManagedCriticalState
81 | let state: ManagedCriticalState
82 |
83 | public init() {
84 | self.ids = ManagedCriticalState(0)
85 | self.state = ManagedCriticalState(.initial)
86 | }
87 |
88 | func generateId() -> Int {
89 | self.ids.withCriticalRegion { ids in
90 | ids += 1
91 | return ids
92 | }
93 | }
94 |
95 | var hasBufferedElements: Bool {
96 | self.state.withCriticalRegion { state in
97 | switch state {
98 | case .idle:
99 | return false
100 | case .queued(let values) where !values.isEmpty:
101 | return true
102 | case .awaiting, .queued:
103 | return false
104 | case .finished:
105 | return true
106 | }
107 | }
108 | }
109 |
110 | func send(_ value: Value) {
111 | let decision = self.state.withCriticalRegion { state -> SendDecision in
112 | switch (state, value) {
113 | case (.idle, .element):
114 | state = .queued([value])
115 | return .nothing
116 | case (.idle, .termination):
117 | state = .finished
118 | return .nothing
119 | case (.queued(var values), _):
120 | values.append(value)
121 | state = .queued(values)
122 | return .nothing
123 | case (.awaiting(var awaitings), .element(let element)):
124 | let awaiting = awaitings.removeFirst()
125 | if awaitings.isEmpty {
126 | state = .idle
127 | } else {
128 | state = .awaiting(awaitings)
129 | }
130 | return .resume(awaiting, element)
131 | case (.awaiting(let awaitings), .termination):
132 | state = .finished
133 | return .terminate(Array(awaitings))
134 | case (.finished, _):
135 | return .nothing
136 | }
137 | }
138 | switch decision {
139 | case .nothing:
140 | break
141 | case .terminate(let awaitings):
142 | awaitings.forEach { $0.continuation?.resume(returning: nil) }
143 | case let .resume(awaiting, element):
144 | awaiting.continuation?.resume(returning: element)
145 | }
146 | }
147 |
148 | public func send(_ element: Element) {
149 | self.send(.element(element))
150 | }
151 |
152 | public func finish() {
153 | self.send(.termination)
154 | }
155 |
156 | func next(onSuspend: (() -> Void)? = nil) async -> Element? {
157 | let awaitingId = self.generateId()
158 | let cancellation = ManagedCriticalState(false)
159 |
160 | return await withTaskCancellationHandler { [state] in
161 | let awaiting = state.withCriticalRegion { state -> Awaiting? in
162 | cancellation.withCriticalRegion { cancellation in
163 | cancellation = true
164 | }
165 | switch state {
166 | case .awaiting(var awaitings):
167 | let awaiting = awaitings.remove(.placeHolder(id: awaitingId))
168 | if awaitings.isEmpty {
169 | state = .idle
170 | } else {
171 | state = .awaiting(awaitings)
172 | }
173 | return awaiting
174 | default:
175 | return nil
176 | }
177 | }
178 |
179 | awaiting?.continuation?.resume(returning: nil)
180 | } operation: {
181 | await withUnsafeContinuation { [state] (continuation: UnsafeContinuation) in
182 | let decision = state.withCriticalRegion { state -> AwaitingDecision in
183 | let isCancelled = cancellation.withCriticalRegion { $0 }
184 | guard !isCancelled else { return .resume(nil) }
185 |
186 | switch state {
187 | case .idle:
188 | state = .awaiting([Awaiting(id: awaitingId, continuation: continuation)])
189 | return .suspend
190 | case .queued(var values):
191 | let value = values.popFirst()
192 | switch value {
193 | case .termination:
194 | state = .finished
195 | return .resume(nil)
196 | case .element(let element) where !values.isEmpty:
197 | state = .queued(values)
198 | return .resume(element)
199 | case .element(let element):
200 | state = .idle
201 | return .resume(element)
202 | default:
203 | state = .idle
204 | return .suspend
205 | }
206 | case .awaiting(var awaitings):
207 | awaitings.updateOrAppend(Awaiting(id: awaitingId, continuation: continuation))
208 | state = .awaiting(awaitings)
209 | return .suspend
210 | case .finished:
211 | return .resume(nil)
212 | }
213 | }
214 |
215 | switch decision {
216 | case .resume(let element): continuation.resume(returning: element)
217 | case .suspend:
218 | onSuspend?()
219 | }
220 | }
221 | }
222 | }
223 |
224 | public func makeAsyncIterator() -> AsyncIterator {
225 | Iterator(
226 | channel: self
227 | )
228 | }
229 |
230 | public struct Iterator: AsyncIteratorProtocol, Sendable {
231 | let channel: AsyncBufferedChannel
232 |
233 | var hasBufferedElements: Bool {
234 | self.channel.hasBufferedElements
235 | }
236 |
237 | public func next() async -> Element? {
238 | await self.channel.next()
239 | }
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/Sources/AsyncSubjects/AsyncCurrentValueSubject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncCurrentValueSubject.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 07/01/2022.
6 | //
7 |
8 | /// A n`AsyncCurrentValueSubject` is an async sequence in which one can send values over time.
9 | /// The current value is always accessible as an instance variable.
10 | /// The current value is replayed in any new async for in loops.
11 | /// When the `AsyncCurrentValueSubject` is terminated, new consumers will
12 | /// immediately resume with this termination.
13 | ///
14 | /// ```
15 | /// let currentValue = AsyncCurrentValueSubject(1)
16 | ///
17 | /// Task {
18 | /// for await element in currentValue {
19 | /// print(element) // will print 1 2
20 | /// }
21 | /// }
22 | ///
23 | /// Task {
24 | /// for await element in currentValue {
25 | /// print(element) // will print 1 2
26 | /// }
27 | /// }
28 | ///
29 | /// .. later in the application flow
30 | ///
31 | /// await currentValue.send(2)
32 | ///
33 | /// print(currentValue.element) // will print 2
34 | /// ```
35 | public final class AsyncCurrentValueSubject: AsyncSubject where Element: Sendable {
36 | public typealias Element = Element
37 | public typealias Failure = Never
38 | public typealias AsyncIterator = Iterator
39 |
40 | struct State: Sendable {
41 | var terminalState: Termination?
42 | var current: Element
43 | var channels: [Int: AsyncBufferedChannel]
44 | var ids: Int
45 | }
46 |
47 | let state: ManagedCriticalState
48 |
49 | public var value: Element {
50 | get {
51 | self.state.criticalState.current
52 | }
53 |
54 | set {
55 | self.send(newValue)
56 | }
57 | }
58 |
59 | /// Creates an AsyncCurrentValueSubject with a current element
60 | /// - Parameter element: the current element
61 | public init(_ element: Element) {
62 | self.state = ManagedCriticalState(
63 | State(terminalState: nil, current: element, channels: [:], ids: 0)
64 | )
65 | }
66 |
67 | /// Sends a value to all consumers
68 | /// - Parameter element: the value to send
69 | public func send(_ element: Element) {
70 | self.state.withCriticalRegion { state in
71 | state.current = element
72 | for channel in state.channels.values {
73 | channel.send(element)
74 | }
75 | }
76 | }
77 |
78 | /// Finishes the async sequences with a normal ending.
79 | /// - Parameter termination: The termination to finish the subject.
80 | public func send(_ termination: Termination) {
81 | self.state.withCriticalRegion { state in
82 | state.terminalState = termination
83 | let channels = Array(state.channels.values)
84 | state.channels.removeAll()
85 | for channel in channels {
86 | channel.finish()
87 | }
88 | }
89 | }
90 |
91 | func handleNewConsumer() -> (iterator: AsyncBufferedChannel.Iterator, unregister: @Sendable () -> Void) {
92 | let asyncBufferedChannel = AsyncBufferedChannel()
93 |
94 | let (terminalState, current) = self.state.withCriticalRegion { state -> (Termination?, Element) in
95 | (state.terminalState, state.current)
96 | }
97 |
98 | if let terminalState = terminalState, terminalState.isFinished {
99 | asyncBufferedChannel.finish()
100 | return (asyncBufferedChannel.makeAsyncIterator(), {})
101 | }
102 |
103 | asyncBufferedChannel.send(current)
104 |
105 | let consumerId = self.state.withCriticalRegion { state -> Int in
106 | state.ids += 1
107 | state.channels[state.ids] = asyncBufferedChannel
108 | return state.ids
109 | }
110 |
111 | let unregister = { @Sendable [state] in
112 | state.withCriticalRegion { state in
113 | state.channels[consumerId] = nil
114 | }
115 | }
116 |
117 | return (asyncBufferedChannel.makeAsyncIterator(), unregister)
118 | }
119 |
120 | public func makeAsyncIterator() -> AsyncIterator {
121 | Iterator(asyncSubject: self)
122 | }
123 |
124 | public struct Iterator: AsyncSubjectIterator {
125 | var iterator: AsyncBufferedChannel.Iterator
126 | let unregister: @Sendable () -> Void
127 |
128 | init(asyncSubject: AsyncCurrentValueSubject) {
129 | (self.iterator, self.unregister) = asyncSubject.handleNewConsumer()
130 | }
131 |
132 | public var hasBufferedElements: Bool {
133 | self.iterator.hasBufferedElements
134 | }
135 |
136 | public mutating func next() async -> Element? {
137 | await withTaskCancellationHandler {
138 | await self.iterator.next()
139 | } onCancel: { [unregister] in
140 | unregister()
141 | }
142 | }
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/Sources/AsyncSubjects/AsyncPassthroughSubject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncPassthroughSubject.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 07/01/2022.
6 | //
7 |
8 | /// An `AsyncPassthroughSubject` is an async sequence in which one can send values over time.
9 | /// When the `AsyncPassthroughSubject` is terminated, new consumers will
10 | /// immediately resume with this termination.
11 | ///
12 | /// ```
13 | /// let passthrough = AsyncPassthroughSubject()
14 | ///
15 | /// Task {
16 | /// for await element in passthrough {
17 | /// print(element) // will print 1 2
18 | /// }
19 | /// }
20 | ///
21 | /// Task {
22 | /// for await element in passthrough {
23 | /// print(element) // will print 1 2
24 | /// }
25 | /// }
26 | ///
27 | /// ... later in the application flow
28 | ///
29 | /// passthrough.send(1)
30 | /// passthrough.send(2)
31 | /// passthrough.send(.finished)
32 | /// ```
33 | public final class AsyncPassthroughSubject: AsyncSubject {
34 | public typealias Element = Element
35 | public typealias Failure = Never
36 | public typealias AsyncIterator = Iterator
37 |
38 | struct State {
39 | var terminalState: Termination?
40 | var channels: [Int: AsyncBufferedChannel]
41 | var ids: Int
42 | }
43 |
44 | let state: ManagedCriticalState
45 |
46 | public init() {
47 | self.state = ManagedCriticalState(
48 | State(terminalState: nil, channels: [:], ids: 0)
49 | )
50 | }
51 |
52 | /// Sends a value to all consumers
53 | /// - Parameter element: the value to send
54 | public func send(_ element: Element) {
55 | self.state.withCriticalRegion { state in
56 | for channel in state.channels.values {
57 | channel.send(element)
58 | }
59 | }
60 | }
61 |
62 | /// Finishes the subject with a normal ending.
63 | /// - Parameter termination: The termination to finish the subject
64 | public func send(_ termination: Termination) {
65 | self.state.withCriticalRegion { state in
66 | state.terminalState = termination
67 | let channels = Array(state.channels.values)
68 | state.channels.removeAll()
69 | for channel in channels {
70 | channel.finish()
71 | }
72 | }
73 | }
74 |
75 | func handleNewConsumer() -> (iterator: AsyncBufferedChannel.Iterator, unregister: @Sendable () -> Void) {
76 | let asyncBufferedChannel = AsyncBufferedChannel()
77 |
78 | let terminalState = self.state.withCriticalRegion { state in
79 | state.terminalState
80 | }
81 |
82 | if let terminalState = terminalState, terminalState.isFinished {
83 | asyncBufferedChannel.finish()
84 | return (asyncBufferedChannel.makeAsyncIterator(), {})
85 | }
86 |
87 | let consumerId = self.state.withCriticalRegion { state -> Int in
88 | state.ids += 1
89 | state.channels[state.ids] = asyncBufferedChannel
90 | return state.ids
91 | }
92 |
93 | let unregister = { @Sendable [state] in
94 | state.withCriticalRegion { state in
95 | state.channels[consumerId] = nil
96 | }
97 | }
98 |
99 | return (asyncBufferedChannel.makeAsyncIterator(), unregister)
100 | }
101 |
102 | public func makeAsyncIterator() -> AsyncIterator {
103 | Iterator(asyncSubject: self)
104 | }
105 |
106 | public struct Iterator: AsyncSubjectIterator {
107 | var iterator: AsyncBufferedChannel.Iterator
108 | let unregister: @Sendable () -> Void
109 |
110 | init(asyncSubject: AsyncPassthroughSubject) {
111 | (self.iterator, self.unregister) = asyncSubject.handleNewConsumer()
112 | }
113 |
114 | public var hasBufferedElements: Bool {
115 | self.iterator.hasBufferedElements
116 | }
117 |
118 | public mutating func next() async -> Element? {
119 | await withTaskCancellationHandler {
120 | await self.iterator.next()
121 | } onCancel: { [unregister] in
122 | unregister()
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Sources/AsyncSubjects/AsyncReplaySubject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncReplaySubject.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/12/2021.
6 | //
7 |
8 | /// An `AsyncReplaySubject` is an async sequence in which one can send values over time.
9 | /// Values are buffered in a FIFO fashion so they can be replayed by new consumers.
10 | /// When the `bufferSize` is outreached the oldest value is dropped.
11 | /// When the `AsyncReplaySubject` is terminated, new consumers will
12 | /// immediately resume with this termination, whether it is a finish or a failure.
13 | ///
14 | /// ```
15 | /// let replay = AsyncReplaySubject(bufferSize: 3)
16 | ///
17 | /// for i in (1...5) { replay.send(i) }
18 | ///
19 | /// for await element in replay {
20 | /// print(element) // will print 3, 4, 5
21 | /// }
22 | /// ```
23 | public final class AsyncReplaySubject: AsyncSubject where Element: Sendable {
24 | public typealias Element = Element
25 | public typealias Failure = Never
26 | public typealias AsyncIterator = Iterator
27 |
28 | struct State {
29 | var terminalState: Termination?
30 | var bufferSize: UInt
31 | var buffer: [Element]
32 | var channels: [Int: AsyncBufferedChannel]
33 | var ids: Int
34 | }
35 |
36 | let state: ManagedCriticalState
37 |
38 | /// Creates a replay subject with a buffer of replayable values
39 | /// - Parameter bufferSize: the number of values that will be replayed to new consumers
40 | public init(bufferSize: UInt) {
41 | self.state = ManagedCriticalState(
42 | State(terminalState: nil, bufferSize: bufferSize, buffer: [], channels: [:], ids: 0)
43 | )
44 | }
45 |
46 | /// Sends a value to all consumers
47 | /// - Parameter element: the value to send
48 | public func send(_ element: Element) {
49 | self.state.withCriticalRegion { state in
50 | if state.buffer.count >= state.bufferSize && !state.buffer.isEmpty {
51 | state.buffer.removeFirst()
52 | }
53 | state.buffer.append(element)
54 | for channel in state.channels.values {
55 | channel.send(element)
56 | }
57 | }
58 | }
59 |
60 | /// Finishes the subject with a normal ending.
61 | /// - Parameter termination: The termination to finish the subject.
62 | public func send(_ termination: Termination) {
63 | self.state.withCriticalRegion { state in
64 | state.terminalState = termination
65 | let channels = Array(state.channels.values)
66 | state.channels.removeAll()
67 | state.buffer.removeAll()
68 | state.bufferSize = 0
69 | for channel in channels {
70 | channel.finish()
71 | }
72 | }
73 | }
74 |
75 | func handleNewConsumer() -> (iterator: AsyncBufferedChannel.Iterator, unregister: @Sendable () -> Void) {
76 | let asyncBufferedChannel = AsyncBufferedChannel()
77 |
78 | let (terminalState, elements) = self.state.withCriticalRegion { state -> (Termination?, [Element]) in
79 | (state.terminalState, state.buffer)
80 | }
81 |
82 | if let terminalState = terminalState, terminalState.isFinished {
83 | asyncBufferedChannel.finish()
84 | return (asyncBufferedChannel.makeAsyncIterator(), {})
85 | }
86 |
87 | for element in elements {
88 | asyncBufferedChannel.send(element)
89 | }
90 |
91 | let consumerId = self.state.withCriticalRegion { state -> Int in
92 | state.ids += 1
93 | state.channels[state.ids] = asyncBufferedChannel
94 | return state.ids
95 | }
96 |
97 | let unregister = { @Sendable [state] in
98 | state.withCriticalRegion { state in
99 | state.channels[consumerId] = nil
100 | }
101 | }
102 |
103 | return (asyncBufferedChannel.makeAsyncIterator(), unregister)
104 | }
105 |
106 | public func makeAsyncIterator() -> AsyncIterator {
107 | Iterator(asyncSubject: self)
108 | }
109 |
110 | public struct Iterator: AsyncSubjectIterator {
111 | var iterator: AsyncBufferedChannel.Iterator
112 | let unregister: @Sendable () -> Void
113 |
114 | init(asyncSubject: AsyncReplaySubject) {
115 | (self.iterator, self.unregister) = asyncSubject.handleNewConsumer()
116 | }
117 |
118 | public var hasBufferedElements: Bool {
119 | self.iterator.hasBufferedElements
120 | }
121 |
122 | public mutating func next() async -> Element? {
123 | await withTaskCancellationHandler {
124 | await self.iterator.next()
125 | } onCancel: { [unregister] in
126 | unregister()
127 | }
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Sources/AsyncSubjects/AsyncSubject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSubject.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 08/01/2022.
6 | //
7 |
8 | #if swift(>=5.7)
9 | public protocol AsyncSubjectable: AnyObject, AsyncSequence, Sendable where AsyncIterator: AsyncSubjectIterator {
10 | func send(_ element: Element)
11 | }
12 |
13 | public protocol AsyncSubjectTerminable {
14 | associatedtype Failure: Error
15 |
16 | func send(_ termination: Termination)
17 | }
18 |
19 | public typealias AsyncSubject = AsyncSubjectable & AsyncSubjectTerminable
20 | #else
21 | public protocol AsyncSubject: AnyObject, AsyncSequence, Sendable where AsyncIterator: AsyncSubjectIterator {
22 | associatedtype Failure: Error
23 |
24 | func send(_ element: Element)
25 | func send(_ termination: Termination)
26 | }
27 | #endif
28 |
29 | public protocol AsyncSubjectIterator: AsyncIteratorProtocol, Sendable {
30 | var hasBufferedElements: Bool { get }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/AsyncSubjects/AsyncThrowingCurrentValueSubject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncThrowingCurrentValueSubject.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 07/01/2022.
6 | //
7 |
8 | /// An`AsyncThrowingCurrentValueSubject` is an async sequence in which one can send values over time.
9 | /// The current value is always accessible as an instance variable.
10 | /// The current value is replayed in any new async for in loops.
11 | /// When the `AsyncThrowingCurrentValueSubject` is terminated, new consumers will
12 | /// immediately resume with this termination, whether it is a finish or a failure.
13 | ///
14 | /// ```
15 | /// let currentValue = AsyncThrowingCurrentValueSubject(1)
16 | ///
17 | /// Task {
18 | /// for try await element in currentValue {
19 | /// print(element) // will print 1 2 and throw
20 | /// }
21 | /// }
22 | ///
23 | /// Task {
24 | /// for try await element in currentValue {
25 | /// print(element) // will print 1 2 and throw
26 | /// }
27 | /// }
28 | ///
29 | /// .. later in the application flow
30 | ///
31 | /// await currentValue.send(2)
32 | ///
33 | /// print(currentValue.element) // will print 2
34 | /// await currentValue.send(.failure(error))
35 | ///
36 | /// ```
37 | public final class AsyncThrowingCurrentValueSubject: AsyncSubject where Element: Sendable {
38 | public typealias Element = Element
39 | public typealias Failure = Failure
40 | public typealias AsyncIterator = Iterator
41 |
42 | struct State {
43 | var terminalState: Termination?
44 | var current: Element
45 | var channels: [Int: AsyncThrowingBufferedChannel]
46 | var ids: Int
47 | }
48 |
49 | let state: ManagedCriticalState
50 |
51 | public var value: Element {
52 | get {
53 | self.state.criticalState.current
54 | }
55 |
56 | set {
57 | self.send(newValue)
58 | }
59 | }
60 |
61 | public init(_ element: Element) {
62 | self.state = ManagedCriticalState(
63 | State(terminalState: nil, current: element, channels: [:], ids: 0)
64 | )
65 | }
66 |
67 | /// Sends a value to all consumers
68 | /// - Parameter element: the value to send
69 | public func send(_ element: Element) {
70 | self.state.withCriticalRegion { state in
71 | state.current = element
72 | for channel in state.channels.values {
73 | channel.send(element)
74 | }
75 | }
76 | }
77 |
78 | /// Finishes the subject with either a normal ending or an error.
79 | /// - Parameter termination: The termination to finish the subject.
80 | public func send(_ termination: Termination) {
81 | self.state.withCriticalRegion { state in
82 | state.terminalState = termination
83 | let channels = Array(state.channels.values)
84 | state.channels.removeAll()
85 | for channel in channels {
86 | switch termination {
87 | case .finished:
88 | channel.finish()
89 | case .failure(let error):
90 | channel.fail(error)
91 | }
92 | }
93 | }
94 | }
95 |
96 | func handleNewConsumer(
97 | ) -> (iterator: AsyncThrowingBufferedChannel.Iterator, unregister: @Sendable () -> Void) {
98 | let asyncBufferedChannel = AsyncThrowingBufferedChannel()
99 |
100 | let (terminalState, current) = self.state.withCriticalRegion { state -> (Termination?, Element) in
101 | (state.terminalState, state.current)
102 | }
103 |
104 | if let terminalState = terminalState {
105 | switch terminalState {
106 | case .finished:
107 | asyncBufferedChannel.finish()
108 | case .failure(let error):
109 | asyncBufferedChannel.fail(error)
110 | }
111 | return (asyncBufferedChannel.makeAsyncIterator(), {})
112 | }
113 |
114 | asyncBufferedChannel.send(current)
115 |
116 | let consumerId = self.state.withCriticalRegion { state -> Int in
117 | state.ids += 1
118 | state.channels[state.ids] = asyncBufferedChannel
119 | return state.ids
120 | }
121 |
122 | let unregister = { @Sendable [state] in
123 | state.withCriticalRegion { state in
124 | state.channels[consumerId] = nil
125 | }
126 | }
127 |
128 | return (asyncBufferedChannel.makeAsyncIterator(), unregister)
129 | }
130 |
131 | public func makeAsyncIterator() -> AsyncIterator {
132 | Iterator(asyncSubject: self)
133 | }
134 |
135 | public struct Iterator: AsyncSubjectIterator {
136 | var iterator: AsyncThrowingBufferedChannel.Iterator
137 | let unregister: @Sendable () -> Void
138 |
139 | init(asyncSubject: AsyncThrowingCurrentValueSubject) {
140 | (self.iterator, self.unregister) = asyncSubject.handleNewConsumer()
141 | }
142 |
143 | public var hasBufferedElements: Bool {
144 | self.iterator.hasBufferedElements
145 | }
146 |
147 | public mutating func next() async throws -> Element? {
148 | try await withTaskCancellationHandler {
149 | try await self.iterator.next()
150 | } onCancel: { [unregister] in
151 | unregister()
152 | }
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/Sources/AsyncSubjects/AsyncThrowingPassthroughSubject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncThrowingPassthroughSubject.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 07/01/2022.
6 | //
7 |
8 | /// An `AsyncThrowingPassthroughSubject` is an async sequence in which one can send values over time.
9 | /// When the `AsyncThrowingPassthroughSubject` is terminated, new consumers will
10 | /// immediately resume with this termination, whether it is a finish or a failure.
11 | ///
12 | /// ```
13 | /// let passthrough = AsyncThrowingPassthroughSubject()
14 | ///
15 | /// Task {
16 | /// for try await element in passthrough {
17 | /// print(element) // will print 1 2 and throw
18 | /// }
19 | /// }
20 | ///
21 | /// Task {
22 | /// for try await element in passthrough {
23 | /// print(element) // will print 1 2 and throw
24 | /// }
25 | /// }
26 | ///
27 | /// ... later in the application flow
28 | ///
29 | /// passthrough.send(1)
30 | /// passthrough.send(2)
31 | /// passthrough.send(.failure(error))
32 | /// ```
33 | ///
34 | public final class AsyncThrowingPassthroughSubject: AsyncSubject where Element: Sendable {
35 | public typealias Element = Element
36 | public typealias Failure = Failure
37 | public typealias AsyncIterator = Iterator
38 |
39 | struct State {
40 | var terminalState: Termination?
41 | var channels: [Int: AsyncThrowingBufferedChannel]
42 | var ids: Int
43 | }
44 |
45 | let state: ManagedCriticalState
46 |
47 | public init() {
48 | self.state = ManagedCriticalState(
49 | State(terminalState: nil, channels: [:], ids: 0)
50 | )
51 | }
52 |
53 | /// Sends a value to all consumers
54 | /// - Parameter element: the value to send
55 | public func send(_ element: Element) {
56 | self.state.withCriticalRegion { state in
57 | for channel in state.channels.values {
58 | channel.send(element)
59 | }
60 | }
61 | }
62 |
63 | /// Finishes the subject with either a normal ending or an error.
64 | /// - Parameter termination: The termination to finish the subject
65 | public func send(_ termination: Termination) {
66 | self.state.withCriticalRegion { state in
67 | state.terminalState = termination
68 | let channels = Array(state.channels.values)
69 | state.channels.removeAll()
70 |
71 | for channel in channels {
72 | switch termination {
73 | case .finished:
74 | channel.finish()
75 | case .failure(let error):
76 | channel.fail(error)
77 | }
78 | }
79 | }
80 | }
81 |
82 | func handleNewConsumer(
83 | ) -> (iterator: AsyncThrowingBufferedChannel.Iterator, unregister: @Sendable () -> Void) {
84 | let asyncBufferedChannel = AsyncThrowingBufferedChannel()
85 |
86 | let terminalState = self.state.withCriticalRegion { state in
87 | state.terminalState
88 | }
89 |
90 | if let terminalState = terminalState {
91 | switch terminalState {
92 | case .finished:
93 | asyncBufferedChannel.finish()
94 | case .failure(let error):
95 | asyncBufferedChannel.fail(error)
96 | }
97 | return (asyncBufferedChannel.makeAsyncIterator(), {})
98 | }
99 |
100 | let consumerId = self.state.withCriticalRegion { state -> Int in
101 | state.ids += 1
102 | state.channels[state.ids] = asyncBufferedChannel
103 | return state.ids
104 | }
105 |
106 | let unregister = { @Sendable [state] in
107 | state.withCriticalRegion { state in
108 | state.channels[consumerId] = nil
109 | }
110 | }
111 |
112 | return (asyncBufferedChannel.makeAsyncIterator(), unregister)
113 | }
114 |
115 | public func makeAsyncIterator() -> AsyncIterator {
116 | Iterator(asyncSubject: self)
117 | }
118 |
119 | public struct Iterator: AsyncSubjectIterator {
120 | var iterator: AsyncThrowingBufferedChannel.Iterator
121 | let unregister: @Sendable () -> Void
122 |
123 | init(asyncSubject: AsyncThrowingPassthroughSubject) {
124 | (self.iterator, self.unregister) = asyncSubject.handleNewConsumer()
125 | }
126 |
127 | public var hasBufferedElements: Bool {
128 | self.iterator.hasBufferedElements
129 | }
130 |
131 | public mutating func next() async throws -> Element? {
132 | try await withTaskCancellationHandler {
133 | try await self.iterator.next()
134 | } onCancel: { [unregister] in
135 | unregister()
136 | }
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/Sources/AsyncSubjects/AsyncThrowingReplaySubject.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncThrowingReplaySubject.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/12/2021.
6 | //
7 |
8 | /// An `AsyncThrowingReplaySubject` is an async sequence in which one can send values over time.
9 | /// Values are buffered in a FIFO fashion so they can be replayed by new consumers.
10 | /// When the `bufferSize` is outreached the oldest value is dropped.
11 | /// When the `AsyncThrowingReplaySubject` is terminated, new consumers will
12 | /// immediately resume with this termination, whether it is a finish or a failure.
13 | ///
14 | /// ```
15 | /// let replay = AsyncThrowingReplaySubject(bufferSize: 3)
16 | ///
17 | /// for i in (1...5) { replay.send(i) }
18 | /// replay.senf(.failure(error))
19 | ///
20 | /// for try await element in replay {
21 | /// print(element) // will print 3, 4, 5 and throw
22 | /// }
23 | /// ```
24 | public final class AsyncThrowingReplaySubject: AsyncSubject where Element: Sendable {
25 | public typealias Element = Element
26 | public typealias Failure = Failure
27 | public typealias AsyncIterator = Iterator
28 |
29 | struct State {
30 | var terminalState: Termination?
31 | var bufferSize: UInt
32 | var buffer: [Element]
33 | var channels: [Int: AsyncThrowingBufferedChannel]
34 | var ids: Int
35 | }
36 |
37 | let state: ManagedCriticalState
38 |
39 | public init(bufferSize: UInt) {
40 | self.state = ManagedCriticalState(
41 | State(terminalState: nil, bufferSize: bufferSize, buffer: [], channels: [:], ids: 0)
42 | )
43 | }
44 |
45 | /// Sends a value to all consumers
46 | /// - Parameter element: the value to send
47 | public func send(_ element: Element) {
48 | self.state.withCriticalRegion { state in
49 | if state.buffer.count >= state.bufferSize && !state.buffer.isEmpty {
50 | state.buffer.removeFirst()
51 | }
52 | state.buffer.append(element)
53 | for channel in state.channels.values {
54 | channel.send(element)
55 | }
56 | }
57 | }
58 |
59 | /// Finishes the subject with either a normal ending or an error.
60 | /// - Parameter termination: The termination to finish the subject
61 | public func send(_ termination: Termination) {
62 | self.state.withCriticalRegion { state in
63 | state.terminalState = termination
64 | let channels = Array(state.channels.values)
65 | state.channels.removeAll()
66 | state.buffer.removeAll()
67 | state.bufferSize = 0
68 | for channel in channels {
69 | switch termination {
70 | case .finished:
71 | channel.finish()
72 | case .failure(let error):
73 | channel.fail(error)
74 | }
75 | }
76 | }
77 | }
78 |
79 | func handleNewConsumer(
80 | ) -> (iterator: AsyncThrowingBufferedChannel.Iterator, unregister: @Sendable () -> Void) {
81 | let asyncBufferedChannel = AsyncThrowingBufferedChannel()
82 |
83 | let (terminalState, elements) = self.state.withCriticalRegion { state -> (Termination?, [Element]) in
84 | (state.terminalState, state.buffer)
85 | }
86 |
87 | if let terminalState = terminalState {
88 | switch terminalState {
89 | case .finished:
90 | asyncBufferedChannel.finish()
91 | case .failure(let error):
92 | asyncBufferedChannel.fail(error)
93 | }
94 | return (asyncBufferedChannel.makeAsyncIterator(), {})
95 | }
96 |
97 | for element in elements {
98 | asyncBufferedChannel.send(element)
99 | }
100 |
101 | let consumerId = self.state.withCriticalRegion { state -> Int in
102 | state.ids += 1
103 | state.channels[state.ids] = asyncBufferedChannel
104 | return state.ids
105 | }
106 |
107 | let unregister = { @Sendable [state] in
108 | state.withCriticalRegion { state in
109 | state.channels[consumerId] = nil
110 | }
111 | }
112 |
113 | return (asyncBufferedChannel.makeAsyncIterator(), unregister)
114 | }
115 |
116 | public func makeAsyncIterator() -> AsyncIterator {
117 | Iterator(asyncSubject: self)
118 | }
119 |
120 | public struct Iterator: AsyncSubjectIterator {
121 | var iterator: AsyncThrowingBufferedChannel.Iterator
122 | let unregister: @Sendable () -> Void
123 |
124 | init(asyncSubject: AsyncThrowingReplaySubject) {
125 | (self.iterator, self.unregister) = asyncSubject.handleNewConsumer()
126 | }
127 |
128 | public var hasBufferedElements: Bool {
129 | self.iterator.hasBufferedElements
130 | }
131 |
132 | public mutating func next() async throws -> Element? {
133 | try await withTaskCancellationHandler {
134 | try await self.iterator.next()
135 | } onCancel: { [unregister] in
136 | unregister()
137 | }
138 | }
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/Sources/AsyncSubjects/Streamed.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Streamed.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 20/03/2022.
6 | //
7 |
8 | /// A type that streams a property marked with an attribute as an AsyncSequence.
9 | ///
10 | /// Streaming a property with the `@Streamed` attribute creates an AsyncSequence of this type.
11 | /// You access the AsyncSequence with the `$` operator, as shown here:
12 | ///
13 | /// class Weather {
14 | /// @Streamed var temperature: Double
15 | /// init(temperature: Double) {
16 | /// self.temperature = temperature
17 | /// }
18 | /// }
19 | ///
20 | /// let weather = Weather(temperature: 20)
21 | /// Task {
22 | /// for try await element in weather.$temperature {
23 | /// print ("Temperature now: \(element)")
24 | /// }
25 | /// }
26 | ///
27 | /// // ... later in the application flow
28 | ///
29 | /// weather.temperature = 25
30 | ///
31 | /// // Prints:
32 | /// // Temperature now: 20.0
33 | /// // Temperature now: 25.0
34 | @propertyWrapper
35 | public struct Streamed where Element: Sendable {
36 | let currentValue: AsyncCurrentValueSubject
37 |
38 | /// Creates the streamed instance with an initial wrapped value.
39 | ///
40 | /// Don't use this initializer directly. Instead, create a property with the `@Streamed` attribute, as shown here:
41 | ///
42 | /// @Streamed var lastUpdated: Date = Date()
43 | ///
44 | /// - Parameter wrappedValue: The stream's initial value.
45 | public init(wrappedValue: Element) {
46 | self.currentValue = AsyncCurrentValueSubject(wrappedValue)
47 | self.wrappedValue = wrappedValue
48 | }
49 |
50 | public var wrappedValue: Element {
51 | willSet {
52 | self.currentValue.value = newValue
53 | }
54 | }
55 |
56 | /// The property for which this instance exposes an AsyncSequence.
57 | ///
58 | /// The ``Streamed/projectedValue`` is the property accessed with the `$` operator.
59 | public var projectedValue: AnyAsyncSequence {
60 | self.currentValue.eraseToAnyAsyncSequence()
61 | }
62 | }
63 |
64 | extension Streamed: Sendable where Element: Sendable {}
65 |
--------------------------------------------------------------------------------
/Sources/Combiners/Merge/AsyncMerge2Sequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncMerge2Sequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/03/2022.
6 | //
7 |
8 | /// Creates an asynchronous sequence of elements from two underlying asynchronous sequences
9 | public func merge(
10 | _ base1: Base1,
11 | _ base2: Base2
12 | ) -> AsyncMerge2Sequence {
13 | AsyncMerge2Sequence(base1, base2)
14 | }
15 |
16 | /// An asynchronous sequence of elements from two underlying asynchronous sequences
17 | ///
18 | /// In a `AsyncMerge2Sequence` instance, the *i*th element is the *i*th element
19 | /// resolved in sequential order out of the two underlying asynchronous sequences.
20 | /// Use the `merge(_:_:)` function to create an `AsyncMerge2Sequence`.
21 | public struct AsyncMerge2Sequence: AsyncSequence
22 | where Base1.Element == Base2.Element {
23 | public typealias Element = Base1.Element
24 | public typealias AsyncIterator = Iterator
25 |
26 | let base1: Base1
27 | let base2: Base2
28 |
29 | public init(_ base1: Base1, _ base2: Base2) {
30 | self.base1 = base1
31 | self.base2 = base2
32 | }
33 |
34 | public func makeAsyncIterator() -> Iterator {
35 | Iterator(
36 | base1: self.base1,
37 | base2: self.base2
38 | )
39 | }
40 |
41 | public struct Iterator: AsyncIteratorProtocol {
42 | let mergeStateMachine: MergeStateMachine
43 |
44 | init(base1: Base1, base2: Base2) {
45 | self.mergeStateMachine = MergeStateMachine(
46 | base1,
47 | base2
48 | )
49 | }
50 |
51 | public mutating func next() async rethrows -> Element? {
52 | let mergedElement = await self.mergeStateMachine.next()
53 | switch mergedElement {
54 | case .element(let result):
55 | return try result._rethrowGet()
56 | case .termination:
57 | return nil
58 | }
59 | }
60 | }
61 | }
62 |
63 | extension AsyncMerge2Sequence: Sendable where Base1: Sendable, Base2: Sendable {}
64 |
--------------------------------------------------------------------------------
/Sources/Combiners/Merge/AsyncMerge3Sequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncMerge3Sequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/03/2022.
6 | //
7 |
8 | /// Creates an asynchronous sequence of elements from three underlying asynchronous sequences
9 | public func merge(
10 | _ base1: Base1,
11 | _ base2: Base2,
12 | _ base3: Base3
13 | ) -> AsyncMerge3Sequence {
14 | AsyncMerge3Sequence(base1, base2, base3)
15 | }
16 |
17 | /// An asynchronous sequence of elements from three underlying asynchronous sequences
18 | ///
19 | /// In a `AsyncMerge3Sequence` instance, the *i*th element is the *i*th element
20 | /// resolved in sequential order out of the two underlying asynchronous sequences.
21 | /// Use the `merge(_:_:_:)` function to create an `AsyncMerge3Sequence`.
22 | public struct AsyncMerge3Sequence: AsyncSequence
23 | where Base1.Element == Base2.Element, Base3.Element == Base2.Element {
24 | public typealias Element = Base1.Element
25 | public typealias AsyncIterator = Iterator
26 |
27 | let base1: Base1
28 | let base2: Base2
29 | let base3: Base3
30 |
31 | public init(_ base1: Base1, _ base2: Base2, _ base3: Base3) {
32 | self.base1 = base1
33 | self.base2 = base2
34 | self.base3 = base3
35 | }
36 |
37 | public func makeAsyncIterator() -> Iterator {
38 | Iterator(
39 | base1: self.base1,
40 | base2: self.base2,
41 | base3: self.base3
42 | )
43 | }
44 |
45 | public struct Iterator: AsyncIteratorProtocol {
46 | let mergeStateMachine: MergeStateMachine
47 |
48 | init(base1: Base1, base2: Base2, base3: Base3) {
49 | self.mergeStateMachine = MergeStateMachine(
50 | base1,
51 | base2,
52 | base3
53 | )
54 | }
55 |
56 | public mutating func next() async rethrows -> Element? {
57 | let mergedElement = await self.mergeStateMachine.next()
58 | switch mergedElement {
59 | case .element(let result):
60 | return try result._rethrowGet()
61 | case .termination:
62 | return nil
63 | }
64 | }
65 | }
66 | }
67 |
68 | extension AsyncMerge3Sequence: Sendable where Base1: Sendable, Base2: Sendable, Base3: Sendable {}
69 | extension AsyncMerge3Sequence.Iterator: Sendable where Base1: Sendable, Base2: Sendable, Base3: Sendable {}
70 |
--------------------------------------------------------------------------------
/Sources/Combiners/Merge/AsyncMergeSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncMergeSequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/03/2022.
6 | //
7 |
8 | /// Creates an asynchronous sequence of elements from many underlying asynchronous sequences
9 | public func merge(
10 | _ bases: Base...
11 | ) -> AsyncMergeSequence {
12 | AsyncMergeSequence(bases)
13 | }
14 |
15 | /// An asynchronous sequence of elements from many underlying asynchronous sequences
16 | ///
17 | /// In a `AsyncMergeSequence` instance, the *i*th element is the *i*th element
18 | /// resolved in sequential order out of the two underlying asynchronous sequences.
19 | /// Use the `merge(...)` function to create an `AsyncMergeSequence`.
20 | public struct AsyncMergeSequence: AsyncSequence {
21 | public typealias Element = Base.Element
22 | public typealias AsyncIterator = Iterator
23 |
24 | let bases: [Base]
25 |
26 | public init(_ bases: [Base]) {
27 | self.bases = bases
28 | }
29 |
30 | public func makeAsyncIterator() -> Iterator {
31 | Iterator(
32 | bases: self.bases
33 | )
34 | }
35 |
36 | public struct Iterator: AsyncIteratorProtocol {
37 | let mergeStateMachine: MergeStateMachine
38 |
39 | init(bases: [Base]) {
40 | self.mergeStateMachine = MergeStateMachine(
41 | bases
42 | )
43 | }
44 |
45 | public mutating func next() async rethrows -> Element? {
46 | let mergedElement = await self.mergeStateMachine.next()
47 | switch mergedElement {
48 | case .element(let result):
49 | return try result._rethrowGet()
50 | case .termination:
51 | return nil
52 | }
53 | }
54 | }
55 | }
56 |
57 | extension AsyncMergeSequence: Sendable where Base: Sendable {}
58 |
--------------------------------------------------------------------------------
/Sources/Combiners/WithLatestFrom/AsyncWithLatestFromSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncWithLatestFromSequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/03/2022.
6 | //
7 |
8 | public extension AsyncSequence {
9 | /// Combines `self` with another ``AsyncSequence`` into a single ``AsyncSequence`` where each
10 | /// element from `self` is aggregated to the latest known element from the `other` sequence (if any) as a tuple.
11 | ///
12 | /// Remark: as the `other` sequence is being iterated over in the context of its own ``Task``, there is no guarantee
13 | /// that its latest know element is the one that has just been produced when the base sequence produces its next element.
14 | ///
15 | /// ```
16 | /// let base = AsyncPassthoughSubject()
17 | /// let other = AsyncPassthoughSubject()
18 | /// let sequence = base.withLatest(from: other)
19 | ///
20 | /// Task {
21 | /// for element in await sequence {
22 | /// print(element)
23 | /// }
24 | /// }
25 | ///
26 | /// await other.send("a")
27 | /// await other.send("b")
28 | ///
29 | /// ... later in the application flow
30 | ///
31 | /// await base.send(1)
32 | ///
33 | /// // will print: (1, "b")
34 | /// ```
35 | ///
36 | /// - Parameter other: the other ``AsyncSequence``
37 | /// - Returns: an ``AsyncWithLatestFromSequence`` where elements are a tuple of an element from `self` and the
38 | /// latest known element (if any) from the `other` sequence.
39 | func withLatest(
40 | from other: Other
41 | ) -> AsyncWithLatestFromSequence {
42 | AsyncWithLatestFromSequence(self, other)
43 | }
44 | }
45 |
46 | /// ``AsyncWithLatestFromSequence`` is an ``AsyncSequence`` where elements are a tuple of an element from `base` and the
47 | /// latest known element (if any) from the `other` sequence.
48 | public struct AsyncWithLatestFromSequence: AsyncSequence
49 | where Other: Sendable, Other.Element: Sendable {
50 | public typealias Element = (Base.Element, Other.Element)
51 | public typealias AsyncIterator = Iterator
52 |
53 | let base: Base
54 | let other: Other
55 |
56 | // for testability purpose
57 | var onBaseElement: (@Sendable (Base.Element) -> Void)?
58 | var onOtherElement: (@Sendable (Other.Element?) -> Void)?
59 |
60 | init(_ base: Base, _ other: Other) {
61 | self.base = base
62 | self.other = other
63 | }
64 |
65 | public func makeAsyncIterator() -> Iterator {
66 | var iterator = Iterator(
67 | base: self.base.makeAsyncIterator(),
68 | other: self.other
69 | )
70 | iterator.onBaseElement = onBaseElement
71 | iterator.onOtherElement = onOtherElement
72 | iterator.startOther()
73 | return iterator
74 | }
75 |
76 | public struct Iterator: AsyncIteratorProtocol {
77 | enum OtherState {
78 | case idle
79 | case element(Result)
80 | }
81 |
82 | enum BaseDecision {
83 | case pass
84 | case returnElement(Result)
85 | }
86 |
87 | var base: Base.AsyncIterator
88 | let other: Other
89 | let otherState: ManagedCriticalState
90 | var otherTask: Task?
91 | var isTerminated: Bool
92 |
93 | // for testability purpose
94 | var onBaseElement: (@Sendable (Base.Element) -> Void)?
95 | var onOtherElement: (@Sendable (Other.Element?) -> Void)?
96 |
97 | public init(base: Base.AsyncIterator, other: Other) {
98 | self.base = base
99 | self.other = other
100 | self.otherState = ManagedCriticalState(.idle)
101 | self.isTerminated = false
102 | }
103 |
104 | mutating func startOther() {
105 | self.otherTask = Task { [other, otherState, onOtherElement] in
106 | do {
107 | for try await element in other {
108 | otherState.withCriticalRegion { state in
109 | state = .element(.success(element))
110 | }
111 | onOtherElement?(element)
112 | }
113 | } catch {
114 | otherState.withCriticalRegion { state in
115 | state = .element(.failure(error))
116 | }
117 | }
118 | }
119 | }
120 |
121 | public mutating func next() async rethrows -> Element? {
122 | guard !self.isTerminated else { return nil }
123 |
124 | return try await withTaskCancellationHandler { [otherTask] in
125 | otherTask?.cancel()
126 | } operation: { [otherTask, otherState, onBaseElement] in
127 | do {
128 | while true {
129 | guard let baseElement = try await self.base.next() else {
130 | self.isTerminated = true
131 | otherTask?.cancel()
132 | return nil
133 | }
134 |
135 | onBaseElement?(baseElement)
136 |
137 | let decision = otherState.withCriticalRegion { state -> BaseDecision in
138 | switch state {
139 | case .idle:
140 | return .pass
141 | case .element(.success(let otherElement)):
142 | return .returnElement(.success((baseElement, otherElement)))
143 | case .element(.failure(let otherError)):
144 | return .returnElement(.failure(otherError))
145 | }
146 | }
147 |
148 | switch decision {
149 | case .pass:
150 | continue
151 | case .returnElement(let result):
152 | return try result._rethrowGet()
153 | }
154 | }
155 | } catch {
156 | self.isTerminated = true
157 | otherTask?.cancel()
158 | throw error
159 | }
160 | }
161 | }
162 | }
163 | }
164 |
165 | extension AsyncWithLatestFromSequence: Sendable where Base: Sendable {}
166 |
--------------------------------------------------------------------------------
/Sources/Combiners/Zip/AsyncZip2Sequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncZip2Sequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 13/01/2022.
6 | //
7 |
8 | /// `zip` produces an `AsyncSequence` that combines the latest elements from two sequences according to their temporality
9 | /// and emits a tuple to the client. If any Async Sequence ends successfully or fails with an error, so to does the zipped
10 | /// Async Sequence.
11 | ///
12 | /// ```
13 | /// let asyncSequence1 = [1, 2, 3, 4, 5].async
14 | /// let asyncSequence2 = ["1", "2", "3", "4", "5"].async
15 | ///
16 | /// let zippedAsyncSequence = zip(asyncSequence1, asyncSequence2)
17 | ///
18 | /// for await element in zippedAsyncSequence {
19 | /// print(element) // will print -> (1, "1") (2, "2") (3, "3") (4, "4") (5, "5")
20 | /// }
21 | /// ```
22 | /// Use the `zip(_:_:)` function to create an `AsyncZip2Sequence`.
23 | public func zip(
24 | _ base1: Base1,
25 | _ base2: Base2
26 | ) -> AsyncZip2Sequence {
27 | AsyncZip2Sequence(base1, base2)
28 | }
29 |
30 | public struct AsyncZip2Sequence: AsyncSequence
31 | where Base1: Sendable, Base1.Element: Sendable, Base2: Sendable, Base2.Element: Sendable {
32 | public typealias Element = (Base1.Element, Base2.Element)
33 | public typealias AsyncIterator = Iterator
34 |
35 | let base1: Base1
36 | let base2: Base2
37 |
38 | init(_ base1: Base1, _ base2: Base2) {
39 | self.base1 = base1
40 | self.base2 = base2
41 | }
42 |
43 | public func makeAsyncIterator() -> AsyncIterator {
44 | Iterator(
45 | base1,
46 | base2
47 | )
48 | }
49 |
50 | public struct Iterator: AsyncIteratorProtocol {
51 | let runtime: Zip2Runtime
52 |
53 | init(_ base1: Base1, _ base2: Base2) {
54 | self.runtime = Zip2Runtime(base1, base2)
55 | }
56 |
57 | public func next() async rethrows -> Element? {
58 | try await self.runtime.next()
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Sources/Combiners/Zip/AsyncZip3Sequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncZip3Sequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 24/09/2022.
6 | //
7 |
8 | /// `zip` produces an `AsyncSequence` that combines the latest elements from three sequences according to their temporality
9 | /// and emits a tuple to the client. If any Async Sequence ends successfully or fails with an error, so to does the zipped
10 | /// Async Sequence.
11 | ///
12 | /// ```
13 | /// let asyncSequence1 = [1, 2, 3, 4, 5].async
14 | /// let asyncSequence2 = ["1", "2", "3", "4", "5"].async
15 | /// let asyncSequence3 = ["A", "B", "C", "D", "E"].async
16 | ///
17 | /// let zippedAsyncSequence = zip(asyncSequence1, asyncSequence2, asyncSequence3)
18 | ///
19 | /// for await element in zippedAsyncSequence {
20 | /// print(element) // will print -> (1, "1", "A") (2, "2", "B") (3, "3", "V") (4, "4", "D") (5, "5", "E")
21 | /// }
22 | /// ```
23 | /// Use the `zip(_:_:_:)` function to create an `AsyncZip3Sequence`.
24 | public func zip(
25 | _ base1: Base1,
26 | _ base2: Base2,
27 | _ base3: Base3
28 | ) -> AsyncZip3Sequence {
29 | AsyncZip3Sequence(base1, base2, base3)
30 | }
31 |
32 | public struct AsyncZip3Sequence: AsyncSequence
33 | where Base1: Sendable, Base1.Element: Sendable, Base2: Sendable, Base2.Element: Sendable, Base3: Sendable, Base3.Element: Sendable {
34 | public typealias Element = (Base1.Element, Base2.Element, Base3.Element)
35 | public typealias AsyncIterator = Iterator
36 |
37 | let base1: Base1
38 | let base2: Base2
39 | let base3: Base3
40 |
41 | init(_ base1: Base1, _ base2: Base2, _ base3: Base3) {
42 | self.base1 = base1
43 | self.base2 = base2
44 | self.base3 = base3
45 | }
46 |
47 | public func makeAsyncIterator() -> AsyncIterator {
48 | Iterator(
49 | base1,
50 | base2,
51 | base3
52 | )
53 | }
54 |
55 | public struct Iterator: AsyncIteratorProtocol {
56 | let runtime: Zip3Runtime
57 |
58 | init(_ base1: Base1, _ base2: Base2, _ base3: Base3) {
59 | self.runtime = Zip3Runtime(base1, base2, base3)
60 | }
61 |
62 | public func next() async rethrows -> Element? {
63 | try await self.runtime.next()
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Sources/Combiners/Zip/AsyncZipSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncZipSequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 24/09/2022.
6 | //
7 |
8 | /// `zip` produces an `AsyncSequence` that combines the latest elements from sequences according to their temporality
9 | /// and emits an array to the client. If any Async Sequence ends successfully or fails with an error, so to does the zipped
10 | /// Async Sequence.
11 | ///
12 | /// ```
13 | /// let asyncSequence1 = [1, 2, 3, 4, 5].async
14 | /// let asyncSequence2 = [1, 2, 3, 4, 5].async
15 | /// let asyncSequence3 = [1, 2, 3, 4, 5].async
16 | /// let asyncSequence4 = [1, 2, 3, 4, 5].async
17 | /// let asyncSequence5 = [1, 2, 3, 4, 5].async
18 | ///
19 | /// let zippedAsyncSequence = zip(asyncSequence1, asyncSequence2, asyncSequence3, asyncSequence4, asyncSequence5)
20 | ///
21 | /// for await element in zippedAsyncSequence {
22 | /// print(element) // will print -> [1, 1, 1, 1, 1] [2, 2, 2, 2, 2] [3, 3, 3, 3, 3] [4, 4, 4, 4, 4] [5, 5, 5, 5, 5]
23 | /// }
24 | /// ```
25 | /// Use the `zip(_:)` function to create an `AsyncZipSequence`.
26 | public func zip(_ bases: Base...) -> AsyncZipSequence {
27 | AsyncZipSequence(bases)
28 | }
29 |
30 | public struct AsyncZipSequence: AsyncSequence
31 | where Base: Sendable, Base.Element: Sendable {
32 | public typealias Element = [Base.Element]
33 | public typealias AsyncIterator = Iterator
34 |
35 | let bases: [Base]
36 |
37 | init(_ bases: [Base]) {
38 | self.bases = bases
39 | }
40 |
41 | public func makeAsyncIterator() -> AsyncIterator {
42 | Iterator(bases)
43 | }
44 |
45 | public struct Iterator: AsyncIteratorProtocol {
46 | let runtime: ZipRuntime
47 |
48 | init(_ bases: [Base]) {
49 | self.runtime = ZipRuntime(bases)
50 | }
51 |
52 | public func next() async rethrows -> Element? {
53 | try await self.runtime.next()
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Combiners/Zip/ZipRuntime.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ZipRuntime.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 24/09/2022.
6 | //
7 |
8 | final class ZipRuntime: Sendable
9 | where Base: Sendable, Base.Element: Sendable {
10 | typealias StateMachine = ZipStateMachine
11 |
12 | private let stateMachine: ManagedCriticalState
13 | private let indexes = ManagedCriticalState(0)
14 |
15 | init(_ bases: [Base]) {
16 | self.stateMachine = ManagedCriticalState(StateMachine(numberOfBases: bases.count))
17 |
18 | self.stateMachine.withCriticalRegion { machine in
19 | machine.taskIsStarted(task: Task {
20 | await withTaskGroup(of: Void.self) { group in
21 | for base in bases {
22 | let index = self.indexes.withCriticalRegion { indexes -> Int in
23 | defer { indexes += 1 }
24 | return indexes
25 | }
26 |
27 | group.addTask {
28 | var baseIterator = base.makeAsyncIterator()
29 |
30 | do {
31 | while true {
32 | await withUnsafeContinuation { (continuation: UnsafeContinuation) in
33 | let output = self.stateMachine.withCriticalRegion { machine in
34 | machine.newLoopFromBase(index: index, suspendedBase: continuation)
35 | }
36 |
37 | self.handle(newLoopFromBaseOutput: output)
38 | }
39 |
40 | guard let element = try await baseIterator.next() else {
41 | break
42 | }
43 |
44 | let output = self.stateMachine.withCriticalRegion { machine in
45 | machine.baseHasProducedElement(index: index, element: element)
46 | }
47 |
48 | self.handle(baseHasProducedElementOutput: output)
49 | }
50 | } catch {
51 | let output = self.stateMachine.withCriticalRegion { machine in
52 | machine.baseHasProducedFailure(error: error)
53 | }
54 |
55 | self.handle(baseHasProducedFailureOutput: output)
56 | }
57 |
58 | let output = self.stateMachine.withCriticalRegion { stateMachine in
59 | stateMachine.baseIsFinished()
60 | }
61 |
62 | self.handle(baseIsFinishedOutput: output)
63 | }
64 | }
65 | }
66 | })
67 | }
68 | }
69 |
70 | private func handle(newLoopFromBaseOutput: StateMachine.NewLoopFromBaseOutput) {
71 | switch newLoopFromBaseOutput {
72 | case .none:
73 | break
74 |
75 | case .resumeBases(let suspendedBases):
76 | suspendedBases.forEach { $0.resume() }
77 |
78 | case .terminate(let task, let suspendedBase, let suspendedDemand):
79 | suspendedBase.resume()
80 | suspendedDemand?.resume(returning: nil)
81 | task?.cancel()
82 | }
83 | }
84 |
85 | private func handle(baseHasProducedElementOutput: StateMachine.BaseHasProducedElementOutput) {
86 | switch baseHasProducedElementOutput {
87 | case .none:
88 | break
89 |
90 | case .resumeDemand(let suspendedDemand, let results):
91 | suspendedDemand?.resume(returning: results)
92 |
93 | case .terminate(let task, let suspendedBases):
94 | suspendedBases?.forEach { $0.resume() }
95 | task?.cancel()
96 | }
97 | }
98 |
99 | private func handle(baseHasProducedFailureOutput: StateMachine.BaseHasProducedFailureOutput) {
100 | switch baseHasProducedFailureOutput {
101 | case .resumeDemandAndTerminate(let task, let suspendedDemand, let suspendedBases, let results):
102 | suspendedDemand?.resume(returning: results)
103 | suspendedBases.forEach { $0.resume() }
104 | task?.cancel()
105 |
106 | case .terminate(let task, let suspendedBases):
107 | suspendedBases?.forEach { $0.resume() }
108 | task?.cancel()
109 | }
110 | }
111 |
112 | private func handle(baseIsFinishedOutput: StateMachine.BaseIsFinishedOutput) {
113 | switch baseIsFinishedOutput {
114 | case .terminate(let task, let suspendedBases, let suspendedDemands):
115 | suspendedBases?.forEach { $0.resume() }
116 | suspendedDemands?.forEach { $0?.resume(returning: nil) }
117 | task?.cancel()
118 | }
119 | }
120 |
121 | func next() async rethrows -> [Base.Element]? {
122 | try await withTaskCancellationHandler {
123 | let output = self.stateMachine.withCriticalRegion { stateMachine in
124 | stateMachine.rootTaskIsCancelled()
125 | }
126 |
127 | self.handle(rootTaskIsCancelledOutput: output)
128 | } operation: {
129 | let results = await withUnsafeContinuation { (continuation: UnsafeContinuation<[Int: Result]?, Never>) in
130 | let output = self.stateMachine.withCriticalRegion { stateMachine in
131 | stateMachine.newDemandFromConsumer(suspendedDemand: continuation)
132 | }
133 |
134 | self.handle(newDemandFromConsumerOutput: output)
135 | }
136 |
137 | guard let results = results else {
138 | return nil
139 | }
140 |
141 | let output = self.stateMachine.withCriticalRegion { stateMachine in
142 | stateMachine.demandIsFulfilled()
143 | }
144 |
145 | self.handle(demandIsFulfilledOutput: output)
146 |
147 | return try results.sorted { $0.key < $1.key }.map { try $0.value._rethrowGet() }
148 | }
149 | }
150 |
151 | private func handle(rootTaskIsCancelledOutput: StateMachine.RootTaskIsCancelledOutput) {
152 | switch rootTaskIsCancelledOutput {
153 | case .terminate(let task, let suspendedBases, let suspendedDemands):
154 | suspendedBases?.forEach { $0.resume() }
155 | suspendedDemands?.forEach { $0?.resume(returning: nil) }
156 | task?.cancel()
157 | }
158 | }
159 |
160 | private func handle(newDemandFromConsumerOutput: StateMachine.NewDemandFromConsumerOutput) {
161 | switch newDemandFromConsumerOutput {
162 | case .none:
163 | break
164 |
165 | case .resumeBases(let suspendedBases):
166 | suspendedBases.forEach { $0.resume() }
167 |
168 | case .terminate(let task, let suspendedBases, let suspendedDemands):
169 | suspendedBases?.forEach { $0.resume() }
170 | suspendedDemands?.forEach { $0?.resume(returning: nil) }
171 | task?.cancel()
172 | }
173 | }
174 |
175 | private func handle(demandIsFulfilledOutput: StateMachine.DemandIsFulfilledOutput) {
176 | switch demandIsFulfilledOutput {
177 | case .none:
178 | break
179 |
180 | case .terminate(let task, let suspendedBases, let suspendedDemands):
181 | suspendedBases?.forEach { $0.resume() }
182 | suspendedDemands?.forEach { $0.resume(returning: nil) }
183 | task?.cancel()
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/Sources/Common/Termination.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Termination.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 17/01/2022.
6 | //
7 |
8 | /// A signal that an async sequence doesn’t produce additional elements, either due to a normal ending or an error.
9 | public enum Termination: Sendable {
10 | /// The sequence finished normally.
11 | case finished
12 | /// The sequence stopped emitting due to the indicated error.
13 | case failure(Failure)
14 |
15 | var isFinished: Bool {
16 | if case .finished = self {
17 | return true
18 | }
19 | return false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Sources/Creators/AsyncEmptySequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncEmptySequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/12/2021.
6 | //
7 |
8 | /// `AsyncEmptySequence` is an AsyncSequence that immediately finishes without emitting values.
9 | ///
10 | /// ```
11 | /// let emptySequence = AsyncEmptySequence()
12 | /// for try await element in emptySequence {
13 | /// // will never be called
14 | /// }
15 | /// ```
16 | public struct AsyncEmptySequence: AsyncSequence, Sendable {
17 | public typealias Element = Element
18 | public typealias AsyncIterator = Iterator
19 |
20 | public init() {}
21 |
22 | public func makeAsyncIterator() -> AsyncIterator {
23 | Iterator()
24 | }
25 |
26 | public struct Iterator: AsyncIteratorProtocol, Sendable {
27 | public func next() async -> Element? {
28 | nil
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Sources/Creators/AsyncFailSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncFailSequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 04/01/2022.
6 | //
7 |
8 | /// `AsyncFailSequence` is an AsyncSequence that outputs no elements and throws an error.
9 | /// If the parent task is cancelled while iterating then the iteration finishes before emitting the error.
10 | ///
11 | /// ```
12 | /// let failSequence = AsyncFailSequence(NSError(domain: "", code: 1))
13 | /// do {
14 | /// for try await element in failSequence {
15 | /// // will never be called
16 | /// }
17 | /// } catch {
18 | /// // will catch `NSError(domain: "", code: 1)` here
19 | /// }
20 | /// ```
21 | public struct AsyncFailSequence: AsyncSequence, Sendable {
22 | public typealias Element = Element
23 | public typealias AsyncIterator = Iterator
24 |
25 | let error: Error
26 |
27 | public init(_ error: Failure) {
28 | self.error = error
29 | }
30 |
31 | public func makeAsyncIterator() -> AsyncIterator {
32 | Iterator(error: self.error)
33 | }
34 |
35 | public struct Iterator: AsyncIteratorProtocol, Sendable {
36 | var error: Error?
37 |
38 | public mutating func next() async throws -> Element? {
39 | guard !Task.isCancelled else { return nil }
40 |
41 | guard let error = self.error else {
42 | return nil
43 | }
44 |
45 | self.error = nil
46 | throw error
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/Creators/AsyncJustSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncJustSequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 04/01/2022.
6 | //
7 |
8 | /// `AsyncJustSequence` is an AsyncSequence that outputs a single value and finishes.
9 | /// If the parent task is cancelled while iterating then the iteration finishes before emitting the value.
10 | ///
11 | /// ```
12 | /// let justSequence = AsyncJustSequence(1)
13 | /// for await element in justSequence {
14 | /// // will be called once with element = 1
15 | /// }
16 | /// ```
17 | public struct AsyncJustSequence: AsyncSequence {
18 | public typealias Element = Element
19 | public typealias AsyncIterator = Iterator
20 |
21 | let element: Element?
22 |
23 | public init(_ element: Element?) {
24 | self.element = element
25 | }
26 |
27 | public func makeAsyncIterator() -> AsyncIterator {
28 | Iterator(element: self.element)
29 | }
30 |
31 | public struct Iterator: AsyncIteratorProtocol {
32 | let element: Element?
33 | let isConsumed = ManagedCriticalState(false)
34 |
35 | public mutating func next() async -> Element? {
36 | guard !Task.isCancelled else { return nil }
37 |
38 | let shouldEarlyReturn = self.isConsumed.withCriticalRegion { isConsumed -> Bool in
39 | if !isConsumed {
40 | isConsumed = true
41 | return false
42 | }
43 | return true
44 | }
45 |
46 | if shouldEarlyReturn { return nil }
47 |
48 | return self.element
49 | }
50 | }
51 | }
52 |
53 | extension AsyncJustSequence: Sendable where Element: Sendable {}
54 | extension AsyncJustSequence.Iterator: Sendable where Element: Sendable {}
55 |
--------------------------------------------------------------------------------
/Sources/Creators/AsyncLazySequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncLazySequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 01/01/2022.
6 | //
7 |
8 | public extension Sequence {
9 | /// Creates an AsyncSequence of the sequence elements.
10 | /// - Returns: The AsyncSequence that outputs the elements from the sequence.
11 | var async: AsyncLazySequence {
12 | AsyncLazySequence(self)
13 | }
14 | }
15 |
16 | /// `AsyncLazySequence` is an AsyncSequence that outputs elements from a traditional Sequence.
17 | /// If the parent task is cancelled while iterating then the iteration finishes.
18 | ///
19 | /// ```
20 | /// let fromSequence = AsyncLazySequence([1, 2, 3, 4, 5])
21 | ///
22 | /// for await element in fromSequence {
23 | /// print(element) // will print 1 2 3 4 5
24 | /// }
25 | /// ```
26 | public struct AsyncLazySequence: AsyncSequence {
27 | public typealias Element = Base.Element
28 | public typealias AsyncIterator = Iterator
29 |
30 | private var base: Base
31 |
32 | public init(_ base: Base) {
33 | self.base = base
34 | }
35 |
36 | public func makeAsyncIterator() -> AsyncIterator {
37 | Iterator(base: self.base.makeIterator())
38 | }
39 |
40 | public struct Iterator: AsyncIteratorProtocol {
41 | var base: Base.Iterator
42 |
43 | public mutating func next() async -> Base.Element? {
44 | guard !Task.isCancelled else { return nil }
45 | return self.base.next()
46 | }
47 | }
48 | }
49 |
50 | extension AsyncLazySequence: Sendable where Base: Sendable {}
51 | extension AsyncLazySequence.Iterator: Sendable where Base.Iterator: Sendable {}
52 |
--------------------------------------------------------------------------------
/Sources/Creators/AsyncStream+Pipe.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncStream+Pipe.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 24/09/2022.
6 | //
7 |
8 | public extension AsyncStream {
9 | /// Factory function that creates an AsyncStream and returns a tuple standing for its inputs and outputs.
10 | /// It easy the usage of an AsyncStream in a imperative code context.
11 | /// - Parameter bufferingPolicy: A `Continuation.BufferingPolicy` value to
12 | /// set the stream's buffering behavior. By default, the stream buffers an
13 | /// unlimited number of elements. You can also set the policy to buffer a
14 | /// specified number of oldest or newest elements.
15 | /// - Returns: the tuple (input, output). The input can be yielded with values, the output can be iterated over
16 | static func pipe(
17 | bufferingPolicy: AsyncStream.Continuation.BufferingPolicy = .unbounded
18 | ) -> (AsyncStream.Continuation, AsyncStream) {
19 | var continuation: AsyncStream.Continuation!
20 | let stream = AsyncStream(bufferingPolicy: bufferingPolicy) { continuation = $0 }
21 | return (continuation, stream)
22 | }
23 | }
24 |
25 | public extension AsyncThrowingStream {
26 | /// Factory function that creates an AsyncthrowingStream and returns a tuple standing for its inputs and outputs.
27 | /// It easy the usage of an AsyncthrowingStream in a imperative code context.
28 | /// - Parameter bufferingPolicy: A `Continuation.BufferingPolicy` value to
29 | /// set the stream's buffering behavior. By default, the stream buffers an
30 | /// unlimited number of elements. You can also set the policy to buffer a
31 | /// specified number of oldest or newest elements.
32 | /// - Returns: the tuple (input, output). The input can be yielded with values/errors, the output can be iterated over
33 | static func pipe(
34 | bufferingPolicy: AsyncThrowingStream.Continuation.BufferingPolicy = .unbounded
35 | ) -> (AsyncThrowingStream.Continuation, AsyncThrowingStream) {
36 | var continuation: AsyncThrowingStream.Continuation!
37 | let stream = AsyncThrowingStream(bufferingPolicy: bufferingPolicy) { continuation = $0 }
38 | return (continuation, stream)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/Creators/AsyncThrowingJustSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncThrowingJustSequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 04/01/2022.
6 | //
7 |
8 | /// `AsyncThrowingJustSequence` is an AsyncSequence that outputs a single value from a throwing closure and finishes.
9 | /// If the parent task is cancelled while iterating then the iteration finishes before emitting the value.
10 | ///
11 | /// ```
12 | /// let justSequence = AsyncThrowingJustSequence { 1 }
13 | /// for try await element in justSequence {
14 | /// // will be called once with element = 1
15 | /// }
16 | /// ```
17 | public struct AsyncThrowingJustSequence: AsyncSequence, Sendable {
18 | public typealias Element = Element
19 | public typealias AsyncIterator = Iterator
20 |
21 | let factory: @Sendable () async throws -> Element?
22 |
23 | public init(_ element: Element?) where Element: Sendable {
24 | self.factory = { element }
25 | }
26 |
27 | public init(factory: @Sendable @escaping () async throws -> Element?) {
28 | self.factory = factory
29 | }
30 |
31 | public func makeAsyncIterator() -> AsyncIterator {
32 | Iterator(factory: self.factory)
33 | }
34 |
35 | public struct Iterator: AsyncIteratorProtocol, Sendable {
36 | let factory: @Sendable () async throws -> Element?
37 | let isConsumed = ManagedCriticalState(false)
38 |
39 | public mutating func next() async throws -> Element? {
40 | guard !Task.isCancelled else { return nil }
41 |
42 | let shouldEarlyReturn = self.isConsumed.withCriticalRegion { isConsumed -> Bool in
43 | if !isConsumed {
44 | isConsumed = true
45 | return false
46 | }
47 | return true
48 | }
49 |
50 | if shouldEarlyReturn { return nil }
51 |
52 | let element = try await self.factory()
53 | return element
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Sources/Creators/AsyncTimerSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSequences+Timer.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 04/03/2022.
6 | //
7 |
8 | @preconcurrency import Foundation
9 |
10 | private extension DispatchTimeInterval {
11 | var nanoseconds: UInt64 {
12 | switch self {
13 | case .nanoseconds(let value) where value >= 0: return UInt64(value)
14 | case .microseconds(let value) where value >= 0: return UInt64(value) * 1000
15 | case .milliseconds(let value) where value >= 0: return UInt64(value) * 1_000_000
16 | case .seconds(let value) where value >= 0: return UInt64(value) * 1_000_000_000
17 | case .never: return .zero
18 | default: return .zero
19 | }
20 | }
21 | }
22 |
23 | /// `AsyncTimerSequence`is an async sequence that repeatedly emits the current date on the given interval, with the given priority.
24 | /// The Dates will be buffered until the consumers are available to process them.
25 | ///
26 | /// ```
27 | /// let timer = AsyncTimerSequence(priority: .high, every: .seconds(1))
28 | ///
29 | /// Task {
30 | /// for try await element in timer {
31 | /// print(element)
32 | /// }
33 | /// }
34 | ///
35 | /// // will print:
36 | /// // 2022-03-06 19:31:22 +0000
37 | /// // 2022-03-06 19:31:23 +0000
38 | /// // 2022-03-06 19:31:24 +0000
39 | /// // 2022-03-06 19:31:25 +0000
40 | /// // 2022-03-06 19:31:26 +0000
41 | /// ```
42 | public struct AsyncTimerSequence: AsyncSequence {
43 | public typealias Element = Date
44 | public typealias AsyncIterator = Iterator
45 |
46 | let priority: TaskPriority?
47 | let interval: DispatchTimeInterval
48 |
49 | /// - Parameters:
50 | /// - priority: The priority of the inderlying Task. Nil by default.
51 | /// - interval: The time interval on which to publish events.
52 | /// For example, a value of `.milliseconds(500)`publishes an event approximately every half-second.
53 | /// - Returns: An async sequence that repeatedly emits the current date on the given interval, with the given priority.
54 | public init(priority: TaskPriority? = nil, every interval: DispatchTimeInterval) {
55 | self.priority = priority
56 | self.interval = interval
57 | }
58 |
59 | public func makeAsyncIterator() -> AsyncIterator {
60 | Iterator(priority: self.priority, interval: self.interval)
61 | }
62 |
63 | public struct Iterator: AsyncIteratorProtocol, Sendable {
64 | let asyncChannel: AsyncBufferedChannel
65 | var iterator: AsyncBufferedChannel.Iterator
66 | let task: Task
67 |
68 | init(priority: TaskPriority?, interval: DispatchTimeInterval) {
69 | self.asyncChannel = AsyncBufferedChannel()
70 | self.iterator = self.asyncChannel.makeAsyncIterator()
71 | self.task = Task(priority: priority) { [asyncChannel] in
72 | while !Task.isCancelled {
73 | asyncChannel.send(Date())
74 | try? await Task.sleep(nanoseconds: interval.nanoseconds)
75 | }
76 | asyncChannel.finish()
77 | }
78 | }
79 |
80 | public mutating func next() async -> Element? {
81 | await withTaskCancellationHandler { [task] in
82 | task.cancel()
83 | } operation: {
84 | guard !Task.isCancelled else { return nil }
85 | return await self.iterator.next()
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Sources/Operators/AsyncHandleEventsSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSequence+HandleEvents.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/12/2021.
6 | //
7 |
8 | public extension AsyncSequence {
9 | /// Performs the specified closures when async sequences events occur.
10 | ///
11 | /// ```
12 | /// let sourceSequence = AsyncLazySequence([1, 2, 3, 4, 5])
13 | /// let handledSequence = sourceSequence.handleEvents {
14 | /// print("Begin looping")
15 | /// } onElement: { element in
16 | /// print("Element is \(element)")
17 | /// } onCancel: {
18 | /// print("Cancelled")
19 | /// } onFinish: { termination in
20 | /// print(termination)
21 | /// }
22 | ///
23 | /// for try await element in handledSequence {}
24 | ///
25 | /// // will print:
26 | /// // Begin looping
27 | /// // Element is 1
28 | /// // Element is 2
29 | /// // Element is 3
30 | /// // Element is 4
31 | /// // Element is 5
32 | /// // finished
33 | /// ```
34 | ///
35 | /// - Parameters:
36 | /// - onStart: The operation to execute when the async sequence is first iterated.
37 | /// - onElement: The operation to execute on each element.
38 | /// - onCancel: The operation to execute when the task suppoerting the async sequence looping is cancelled.
39 | /// - onFinish: The operation to execute when the async sequence looping is finished,
40 | /// whether it is due to an error or a normal termination.
41 | /// - Returns: The AsyncSequence that executes the `receiveElement` operation for each element of the source sequence.
42 | func handleEvents(
43 | onStart: (@Sendable () async -> Void)? = nil,
44 | onElement: (@Sendable (Element) async -> Void)? = nil,
45 | onCancel: (@Sendable () async -> Void)? = nil,
46 | onFinish: (@Sendable (Termination) async -> Void)? = nil
47 | ) -> AsyncHandleEventsSequence {
48 | AsyncHandleEventsSequence(
49 | self,
50 | onStart: onStart,
51 | onElement: onElement,
52 | onCancel: onCancel,
53 | onFinish: onFinish
54 | )
55 | }
56 | }
57 |
58 | public struct AsyncHandleEventsSequence: AsyncSequence {
59 | public typealias Element = Base.Element
60 | public typealias AsyncIterator = Iterator
61 |
62 | var base: Base
63 | let onStart: (@Sendable () async -> Void)?
64 | let onElement: (@Sendable (Base.Element) async -> Void)?
65 | let onCancel: (@Sendable () async -> Void)?
66 | let onFinish: (@Sendable (Termination) async -> Void)?
67 |
68 | public init(
69 | _ base: Base,
70 | onStart: (@Sendable () async -> Void)?,
71 | onElement: (@Sendable (Base.Element) async -> Void)?,
72 | onCancel: (@Sendable () async -> Void)?,
73 | onFinish: (@Sendable (Termination) async -> Void)?
74 | ) {
75 | self.base = base
76 | self.onStart = onStart
77 | self.onElement = onElement
78 | self.onCancel = onCancel
79 | self.onFinish = onFinish
80 | }
81 |
82 | public func makeAsyncIterator() -> AsyncIterator {
83 | Iterator(
84 | base: self.base.makeAsyncIterator(),
85 | onStart: self.onStart,
86 | onElement: self.onElement,
87 | onCancel: self.onCancel,
88 | onFinish: self.onFinish
89 | )
90 | }
91 |
92 | public struct Iterator: AsyncIteratorProtocol {
93 | var base: Base.AsyncIterator
94 |
95 | let onStart: (@Sendable () async -> Void)?
96 | let onElement: (@Sendable (Base.Element) async -> Void)?
97 | let onCancel: (@Sendable () async -> Void)?
98 | let onFinish: (@Sendable (Termination) async -> Void)?
99 |
100 | let onStartExecuted = ManagedCriticalState(false)
101 |
102 | public init(
103 | base: Base.AsyncIterator,
104 | onStart: (@Sendable () async -> Void)?,
105 | onElement: (@Sendable (Base.Element) async -> Void)?,
106 | onCancel: (@Sendable () async -> Void)?,
107 | onFinish: (@Sendable (Termination) async -> Void)?
108 | ) {
109 | self.base = base
110 | self.onStart = onStart
111 | self.onElement = onElement
112 | self.onCancel = onCancel
113 | self.onFinish = onFinish
114 | }
115 |
116 | public mutating func next() async rethrows -> Element? {
117 | guard !Task.isCancelled else {
118 | await self.onCancel?()
119 | return nil
120 | }
121 |
122 | let shouldCallOnStart = self.onStartExecuted.withCriticalRegion { onStartExecuted -> Bool in
123 | if !onStartExecuted {
124 | onStartExecuted = true
125 | return true
126 | }
127 | return false
128 | }
129 |
130 | if shouldCallOnStart {
131 | await self.onStart?()
132 | }
133 |
134 | do {
135 | let nextElement = try await self.base.next()
136 |
137 | if let element = nextElement {
138 | await self.onElement?(element)
139 | } else {
140 | if Task.isCancelled {
141 | await self.onCancel?()
142 | } else {
143 | await self.onFinish?(.finished)
144 | }
145 | }
146 |
147 | return nextElement
148 | } catch let error as CancellationError {
149 | await self.onCancel?()
150 | throw error
151 | } catch {
152 | await self.onFinish?(.failure(error))
153 | throw error
154 | }
155 | }
156 | }
157 | }
158 |
159 | extension AsyncHandleEventsSequence: Sendable where Base: Sendable {}
160 | extension AsyncHandleEventsSequence.Iterator: Sendable where Base.AsyncIterator: Sendable {}
161 |
--------------------------------------------------------------------------------
/Sources/Operators/AsyncMapToResultSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncMapToResultSequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 28/08/2022.
6 | //
7 |
8 | public extension AsyncSequence {
9 |
10 | /// Maps elements or failures from a base sequence to a Result. The resulting async sequence cannot throw.
11 | /// - Returns: an async sequence with elements and failure mapped to a Result
12 | ///
13 | /// ```
14 | /// let subject = asyncThrowingPassthroughSubject()
15 | /// let sequence = subject.mapToResult()
16 | /// Task {
17 | /// for await element in sequence {
18 | /// print(element) // will print .success(1), .failure(MyError)
19 | /// }
20 | /// }
21 | ///
22 | /// subject.send(1)
23 | /// subject.send(.failure(MyError()))
24 | /// ```
25 | func mapToResult() -> AsyncMapToResultSequence {
26 | AsyncMapToResultSequence(base: self)
27 | }
28 | }
29 |
30 | public struct AsyncMapToResultSequence: AsyncSequence {
31 | public typealias Element = Result
32 | public typealias AsyncIterator = Iterator
33 |
34 | let base: Base
35 |
36 | public func makeAsyncIterator() -> Iterator {
37 | Iterator(
38 | base: self.base.makeAsyncIterator()
39 | )
40 | }
41 |
42 | public struct Iterator: AsyncIteratorProtocol {
43 | var base: Base.AsyncIterator
44 |
45 | public mutating func next() async -> Element? {
46 | do {
47 | guard let element = try await base.next() else { return nil }
48 | return .success(element)
49 | } catch {
50 | return .failure(error)
51 | }
52 | }
53 | }
54 | }
55 |
56 | extension AsyncMapToResultSequence: Sendable where Base: Sendable {}
57 | extension AsyncMapToResultSequence.Iterator: Sendable where Base.AsyncIterator: Sendable {}
58 |
--------------------------------------------------------------------------------
/Sources/Operators/AsyncMulticastSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSequence+Multicast.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 21/02/2022.
6 | //
7 |
8 | public extension AsyncSequence {
9 | /// Use multicast when you have multiple client iterations, but you want the base async sequence
10 | /// to only produce a single `AsyncIterator`.
11 | /// This is useful when upstream async sequences are doing expensive work you don’t want to duplicate,
12 | /// like performing network requests.
13 | ///
14 | /// The following example uses an async sequence as a counter to emit three random numbers.
15 | /// It uses a ``AsyncSequence/multicast(_:)`` operator with a ``AsyncThrowingPassthroughSubject`
16 | /// to share the same random number to each of two client loops.
17 | /// Because the upstream iterator only begins after a call to ``connect()``.
18 | ///
19 | /// ```
20 | /// let stream = AsyncThrowingPassthroughSubject<(String, Int), Error>()
21 | /// let multicastedAsyncSequence = ["First", "Second", "Third"]
22 | /// .async
23 | /// .map { ($0, Int.random(in: 0...100)) }
24 | /// .handleEvents(onElement: { print("AsyncSequence produces: \($0)") })
25 | /// .multicast(stream)
26 | ///
27 | /// Task {
28 | /// try await multicastedAsyncSequence.collect { print ("Task 1 received: \($0)") }
29 | /// }
30 | ///
31 | /// Task {
32 | /// try await multicastedAsyncSequence.collect { print ("Task 2 received: \($0)") }
33 | /// }
34 | ///
35 | /// multicastedAsyncSequence.connect()
36 | ///
37 | /// // will print:
38 | /// // AsyncSequence produces: ("First", 78)
39 | /// // Stream 2 received: ("First", 78)
40 | /// // Stream 1 received: ("First", 78)
41 | /// // AsyncSequence produces: ("Second", 98)
42 | /// // Stream 2 received: ("Second", 98)
43 | /// // Stream 1 received: ("Second", 98)
44 | /// // AsyncSequence produces: ("Third", 61)
45 | /// // Stream 2 received: ("Third", 61)
46 | /// // Stream 1 received: ("Third", 61)
47 | /// ```
48 | /// In this example, the output shows that the upstream async sequence produces each random value only one time,
49 | /// and then sends the value to both client loops.
50 | ///
51 | /// - Parameter subject: An `AsyncSubject` to deliver elements to downstream client loops.
52 | func multicast(_ subject: S) -> AsyncMulticastSequence
53 | where S.Element == Element, S.Failure == Error {
54 | AsyncMulticastSequence(self, subject: subject)
55 | }
56 | }
57 |
58 | public final class AsyncMulticastSequence: AsyncSequence, Sendable
59 | where Base.Element == Subject.Element, Subject.Failure == Error, Base.AsyncIterator: Sendable {
60 | public typealias Element = Base.Element
61 | public typealias AsyncIterator = Iterator
62 |
63 | enum State {
64 | case available(Base.AsyncIterator)
65 | case busy
66 | }
67 |
68 | let state: ManagedCriticalState
69 | let subject: Subject
70 |
71 | let connectedGate = AsyncReplaySubject(bufferSize: 1)
72 | let isConnected = ManagedCriticalState(false)
73 |
74 | public init(_ base: Base, subject: Subject) {
75 | self.state = ManagedCriticalState(.available(base.makeAsyncIterator()))
76 | self.subject = subject
77 | }
78 |
79 | /// Automates the process of connecting the multicasted async sequence.
80 | ///
81 | /// ```
82 | /// let stream = AsyncPassthroughSubject<(String, Int)>()
83 | /// let multicastedAsyncSequence = ["First", "Second", "Third"]
84 | /// .async
85 | /// .multicast(stream)
86 | /// .autoconnect()
87 | ///
88 | /// try await multicastedAsyncSequence
89 | /// .collect { print ("received: \($0)") }
90 | ///
91 | /// // will print:
92 | /// // received: First
93 | /// // received: Second
94 | /// // received: Third
95 | ///
96 | /// - Returns: A `AsyncMulticastSequence` which automatically connects.
97 | public func autoconnect() -> Self {
98 | self.isConnected.apply(criticalState: true)
99 | return self
100 | }
101 |
102 | /// Allow the `AsyncIterator` to produce elements.
103 | public func connect() {
104 | self.connectedGate.send(())
105 | }
106 |
107 | func next() async {
108 | await Task {
109 | let (canAccessBase, iterator) = self.state.withCriticalRegion { state -> (Bool, Base.AsyncIterator?) in
110 | switch state {
111 | case .available(let iterator):
112 | state = .busy
113 | return (true, iterator)
114 | case .busy:
115 | return (false, nil)
116 | }
117 | }
118 |
119 | guard canAccessBase, var iterator = iterator else { return }
120 |
121 | let toSend: Result
122 | do {
123 | let element = try await iterator.next()
124 | toSend = .success(element)
125 | } catch {
126 | toSend = .failure(error)
127 | }
128 |
129 | self.state.withCriticalRegion { state in
130 | state = .available(iterator)
131 | }
132 |
133 | switch toSend {
134 | case .success(.some(let element)): self.subject.send(element)
135 | case .success(.none): self.subject.send(.finished)
136 | case .failure(let error): self.subject.send(.failure(error))
137 | }
138 | }.value
139 | }
140 |
141 | public func makeAsyncIterator() -> AsyncIterator {
142 | return Iterator(
143 | asyncMulticastSequence: self,
144 | subjectIterator: self.subject.makeAsyncIterator(),
145 | connectedGateIterator: self.connectedGate.makeAsyncIterator(),
146 | isConnected: self.isConnected
147 | )
148 | }
149 |
150 | public struct Iterator: AsyncIteratorProtocol, Sendable {
151 | let asyncMulticastSequence: AsyncMulticastSequence
152 | var subjectIterator: Subject.AsyncIterator
153 |
154 | var connectedGateIterator: AsyncReplaySubject.AsyncIterator
155 | let isConnected: ManagedCriticalState
156 |
157 | public mutating func next() async rethrows -> Element? {
158 | guard !Task.isCancelled else { return nil }
159 |
160 | let shouldWaitForGate = self.isConnected.withCriticalRegion { isConnected -> Bool in
161 | if !isConnected {
162 | isConnected = true
163 | return true
164 | }
165 | return false
166 | }
167 | if shouldWaitForGate {
168 | await self.connectedGateIterator.next()
169 | }
170 |
171 | if !self.subjectIterator.hasBufferedElements {
172 | await self.asyncMulticastSequence.next()
173 | }
174 |
175 | let element = try await self.subjectIterator.next()
176 | return element
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/Sources/Operators/AsyncPrependSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncPrependSequence.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/12/2021.
6 | //
7 |
8 | public extension AsyncSequence {
9 | /// Prepends an element to the upstream async sequence.
10 | ///
11 | /// ```
12 | /// let sourceSequence = AsyncLazySequence([1, 2, 3])
13 | /// let prependSequence = sourceSequence.prepend(0)
14 | ///
15 | /// for try await element in prependSequence {
16 | /// print(element)
17 | /// }
18 | ///
19 | /// // will print:
20 | /// // Element is 0
21 | /// // Element is 1
22 | /// // Element is 2
23 | /// // Element is 3
24 | /// ```
25 | ///
26 | /// - Parameter element: The element to prepend.
27 | /// - Returns: The async sequence prepended with the element.
28 | func prepend(_ element: @Sendable @autoclosure @escaping () -> Element) -> AsyncPrependSequence {
29 | AsyncPrependSequence(self, prependElement: element())
30 | }
31 | }
32 |
33 | public struct AsyncPrependSequence: AsyncSequence {
34 | public typealias Element = Base.Element
35 | public typealias AsyncIterator = Iterator
36 |
37 | private var base: Base
38 | private var prependElement: @Sendable () -> Element
39 |
40 | public init(
41 | _ base: Base,
42 | prependElement: @Sendable @autoclosure @escaping () -> Element
43 | ) {
44 | self.base = base
45 | self.prependElement = prependElement
46 | }
47 |
48 | public func makeAsyncIterator() -> AsyncIterator {
49 | Iterator(
50 | base: self.base.makeAsyncIterator(),
51 | prependElement: self.prependElement
52 | )
53 | }
54 |
55 | public struct Iterator: AsyncIteratorProtocol {
56 | var base: Base.AsyncIterator
57 | var prependElement: () async throws -> Element
58 | var hasBeenDelivered = false
59 |
60 | public init(
61 | base: Base.AsyncIterator,
62 | prependElement: @escaping () async throws -> Element
63 | ) {
64 | self.base = base
65 | self.prependElement = prependElement
66 | }
67 |
68 | public mutating func next() async throws -> Element? {
69 | guard !Task.isCancelled else { return nil }
70 |
71 | if !self.hasBeenDelivered {
72 | self.hasBeenDelivered = true
73 | return try await prependElement()
74 | }
75 |
76 | return try await self.base.next()
77 | }
78 | }
79 | }
80 |
81 | extension AsyncPrependSequence: Sendable where Base: Sendable {}
82 |
--------------------------------------------------------------------------------
/Sources/Operators/AsyncScanSequence.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncSequence+Scan.swift
3 | //
4 | //
5 | // Created by Thibault Wittemberg on 31/12/2021.
6 | //
7 |
8 | public extension AsyncSequence {
9 | /// Transforms elements from the upstream async sequence by providing the current element to a closure
10 | /// along with the last value returned by the closure.
11 | ///
12 | /// ```
13 | /// let sourceSequence = AsyncLazySequence([1, 2, 3, 4, 5])
14 | /// let scannedSequence = sourceSequence.scan("") { accumulator, element in
15 | /// return accumulator + "\(element)"
16 | /// }
17 | /// for try await element in scannedSequence {
18 | /// print(element)
19 | /// }
20 | ///
21 | /// // will print:
22 | /// "1"
23 | /// "12"
24 | /// "123"
25 | /// "1234"
26 | /// "12345"
27 | /// ```
28 | ///
29 | /// - Parameters:
30 | /// - initialResult: The initial value of the result.
31 | /// - nextPartialResult: The closure to execute on each element of the source sequence.
32 | /// - Returns: The async sequence of all the partial results.
33 | func scan