├── .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 | Build Status 6 | 7 | AsyncExtensions supports Swift Package Manager (SPM) 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( 34 | _ initialResult: Output, 35 | _ nextPartialResult: @Sendable @escaping (Output, Element) async -> Output 36 | ) -> AsyncScanSequence { 37 | AsyncScanSequence(self, initialResult: initialResult, nextPartialResult: nextPartialResult) 38 | } 39 | } 40 | 41 | public struct AsyncScanSequence: AsyncSequence { 42 | public typealias Element = Output 43 | public typealias AsyncIterator = Iterator 44 | 45 | var base: Base 46 | var initialResult: Output 47 | let nextPartialResult: @Sendable (Output, Base.Element) async -> Output 48 | 49 | public init( 50 | _ base: Base, 51 | initialResult: Output, 52 | nextPartialResult: @Sendable @escaping (Output, Base.Element) async -> Output 53 | ) { 54 | self.base = base 55 | self.initialResult = initialResult 56 | self.nextPartialResult = nextPartialResult 57 | } 58 | 59 | public func makeAsyncIterator() -> AsyncIterator { 60 | Iterator( 61 | base: self.base.makeAsyncIterator(), 62 | initialResult: self.initialResult, 63 | nextPartialResult: self.nextPartialResult 64 | ) 65 | } 66 | 67 | public struct Iterator: AsyncIteratorProtocol { 68 | var base: Base.AsyncIterator 69 | var currentValue: Output 70 | let nextPartialResult: @Sendable (Output, Base.Element) async -> Output 71 | 72 | public init( 73 | base: Base.AsyncIterator, 74 | initialResult: Output, 75 | nextPartialResult: @Sendable @escaping (Output, Base.Element) async -> Output 76 | ) { 77 | self.base = base 78 | self.currentValue = initialResult 79 | self.nextPartialResult = nextPartialResult 80 | } 81 | 82 | public mutating func next() async rethrows -> Output? { 83 | let nextUpstreamValue = try await self.base.next() 84 | guard let nonNilNextUpstreamValue = nextUpstreamValue else { return nil } 85 | self.currentValue = await self.nextPartialResult(self.currentValue, nonNilNextUpstreamValue) 86 | return self.currentValue 87 | } 88 | } 89 | } 90 | 91 | extension AsyncScanSequence: Sendable where Base: Sendable, Output: Sendable {} 92 | extension AsyncScanSequence.Iterator: Sendable where Base.AsyncIterator: Sendable, Output: Sendable {} 93 | -------------------------------------------------------------------------------- /Sources/Operators/AsyncSequence+Assign.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+Assign.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 02/02/2022. 6 | // 7 | 8 | public extension AsyncSequence { 9 | /// Assigns each element from the async sequence to a property on an object. 10 | /// 11 | /// ``` 12 | /// class Root { 13 | /// var property: String = "" 14 | /// } 15 | /// 16 | /// let root = Root() 17 | /// let sequence = AsyncLazySequence(["1", "2", "3"]) 18 | /// try await sequence.assign(to: \.property, on: root) // will set the property value to "1", "2", "3" 19 | /// ``` 20 | /// 21 | /// - Parameters: 22 | /// - keyPath: A key path that indicates the property to assign. 23 | /// - object: The object that contains the property. 24 | func assign(to keyPath: ReferenceWritableKeyPath, on object: Root) async throws { 25 | for try await element in self { 26 | object[keyPath: keyPath] = element 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/Operators/AsyncSequence+Collect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+Collect.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 31/12/2021. 6 | // 7 | 8 | public extension AsyncSequence { 9 | /// Iterates over each element of the AsyncSequence and give it to the block. 10 | /// 11 | /// ``` 12 | /// let sequence = AsyncLazySequence([1, 2, 3]) 13 | /// sequence.collect { print($0) } // will print 1 2 3 14 | /// ``` 15 | /// 16 | /// - Parameter block: The closure to execute on each element of the async sequence. 17 | func collect(_ block: (Element) async throws -> Void) async rethrows { 18 | for try await element in self { 19 | try await block(element) 20 | } 21 | } 22 | 23 | /// Iterates over each element of the AsyncSequence and returns elements in an Array. 24 | /// 25 | /// ``` 26 | /// let sequence = AsyncLazySequence([1, 2, 3]) 27 | /// let origin = sequence.collect() 28 | /// // origin contains 1 2 3 29 | /// ``` 30 | /// 31 | /// - Returns: the array of elements produced by the AsyncSequence 32 | func collect() async rethrows -> [Element] { 33 | var elements = [Element]() 34 | for try await element in self { 35 | elements.append(element) 36 | } 37 | return elements 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/Operators/AsyncSequence+EraseToAnyAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+EraseToAnyAsyncSequence.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 31/12/2021. 6 | // 7 | 8 | public extension AsyncSequence { 9 | /// Type erase the AsyncSequence into an AnyAsyncSequence. 10 | /// - Returns: A type erased AsyncSequence. 11 | func eraseToAnyAsyncSequence() -> AnyAsyncSequence where Self: Sendable { 12 | AnyAsyncSequence(self) 13 | } 14 | } 15 | 16 | /// Type erased version of an AsyncSequence. 17 | public struct AnyAsyncSequence: AsyncSequence { 18 | public typealias AsyncIterator = AnyAsyncIterator 19 | 20 | private let makeAsyncIteratorClosure: @Sendable () -> AsyncIterator 21 | 22 | public init(_ base: Base) where Base.Element == Element, Base: Sendable { 23 | self.makeAsyncIteratorClosure = { AnyAsyncIterator(base: base.makeAsyncIterator()) } 24 | } 25 | 26 | public func makeAsyncIterator() -> AsyncIterator { 27 | self.makeAsyncIteratorClosure() 28 | } 29 | } 30 | 31 | public struct AnyAsyncIterator: AsyncIteratorProtocol { 32 | public typealias Element = Element 33 | 34 | private let nextClosure: () async throws -> Element? 35 | 36 | public init(base: Base) where Base.Element == Element { 37 | var mutableBase = base 38 | self.nextClosure = { try await mutableBase.next() } 39 | } 40 | 41 | public mutating func next() async throws -> Element? { 42 | try await self.nextClosure() 43 | } 44 | } 45 | 46 | extension AnyAsyncSequence: Sendable {} 47 | -------------------------------------------------------------------------------- /Sources/Operators/AsyncSequence+FlatMapLatest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+FlatMapLatest.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 04/01/2022. 6 | // 7 | 8 | public extension AsyncSequence { 9 | /// Transforms the async sequence elements into a async sequence and flattens the sequence of events 10 | /// from these multiple sources async sequences to appear as if they were coming from a single async sequence of events. 11 | /// Mapping to a new async sequence will cancel the task related to the previous one. 12 | /// 13 | /// ``` 14 | /// let sourceSequence = AsyncSequences.From([1, 2, 3]) 15 | /// let flatMapLatestSequence = sourceSequence.map { element in ["a\(element)", "b\(element)"] } 16 | /// 17 | /// for try await element in flatMapLatestSequence { 18 | /// print(element) 19 | /// } 20 | /// 21 | /// // will print: 22 | /// a3, b3 23 | /// ``` 24 | /// 25 | /// - parameter transform: A transform to apply to each value of the async sequence, from which you can return a new async sequence. 26 | /// - note: This operator is a combination of `map` and `switchToLatest`. 27 | /// - returns: An async sequence emitting the values of the latest inner async sequence. 28 | func flatMapLatest( 29 | _ transform: @Sendable @escaping (Element) async -> OutputAsyncSequence 30 | ) -> AsyncSwitchToLatestSequence> { 31 | self.map(transform).switchToLatest() 32 | } 33 | 34 | /// Transforms the async sequence elements into a async sequence and flattens the sequence of events 35 | /// from these multiple sources async sequences to appear as if they were coming from a single async sequence of events. 36 | /// Mapping to a new async sequence will cancel the task related to the previous one. 37 | /// 38 | /// ``` 39 | /// let sourceSequence = [1, 2, 3].async 40 | /// let flatMapLatestSequence = sourceSequence.map { element in ["a\(element)", "b\(element)"] } 41 | /// 42 | /// for try await element in flatMapLatestSequence { 43 | /// print(element) 44 | /// } 45 | /// 46 | /// // will print: 47 | /// a3, b3 48 | /// ``` 49 | /// 50 | /// - parameter transform: A throwing transformation to apply to each value of the async sequence, 51 | /// from which you can return a new async sequence. 52 | /// - note: This operator is a combination of `map` and `switchToLatest`. 53 | /// - returns: An async sequence emitting the values of the latest inner async sequence. 54 | func flatMapLatest( 55 | _ transform: @Sendable @escaping (Element) async throws -> OutputAsyncSequence 56 | ) -> AsyncSwitchToLatestSequence> { 57 | self.map(transform).switchToLatest() 58 | } 59 | 60 | /// Transforms the async sequence elements into a async element pretty much as a map function would do, 61 | /// except that mapping to a new element will cancel the task related to the previous one. 62 | /// 63 | /// ``` 64 | /// let sourceSequence = [1, 2, 3].async 65 | /// let flatMapLatestSequence = sourceSequence.map { element in await newOutput(element) } // where newOutput is a async function 66 | /// 67 | /// for try await element in flatMapLatestSequence { 68 | /// print(element) 69 | /// } 70 | /// 71 | /// // will print: 72 | /// a3, b3 73 | /// ``` 74 | /// 75 | /// - note: This operator is a combination of `map` and `switchToLatest`. 76 | /// - Returns: An async sequence emitting the value of the latest inner async sequence. 77 | func flatMapLatest( 78 | _ transform: @Sendable @escaping (Element) async throws -> Output 79 | ) -> AsyncSwitchToLatestSequence>> where Element: Sendable { 80 | self.map { element in AsyncThrowingJustSequence { try await transform(element) } }.switchToLatest() 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Sources/Operators/AsyncSequence+Share.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+Share.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 03/03/2022. 6 | // 7 | 8 | public extension AsyncSequence { 9 | /// Shares the output of an upstream async sequence with multiple client loops. 10 | /// 11 | /// - Tip: ``share()`` is effectively a shortcut for ``multicast()`` using a ``AsyncThrowingPassthroughSubject`` 12 | /// stream, with an implicit ``autoconnect()``. 13 | /// 14 | /// The following example uses an async sequence as a counter to emit three random numbers. 15 | /// Each element is delayed by 1s to give the seconf loop a chance to catch all the values. 16 | /// 17 | /// ``` 18 | /// let sharedAsyncSequence = AsyncLazySequence(["first", "second", "third"]) 19 | /// .map { ($0, Int.random(in: 0...100)) } 20 | /// .handleEvents(onElement: { print("AsyncSequence produces: \($0)") }) 21 | /// .share() 22 | /// 23 | /// Task { 24 | /// try await sharedAsyncSequence.collect { print ("Task 1 received: \($0)") } 25 | /// } 26 | /// 27 | /// Task { 28 | /// try await sharedAsyncSequence.collect { print ("Task 2 received: \($0)") } 29 | /// } 30 | /// 31 | /// // will print: 32 | /// // AsyncSequence produces: ("First", 78) 33 | /// // Stream 2 received: ("First", 78) 34 | /// // Stream 1 received: ("First", 78) 35 | /// // AsyncSequence produces: ("Second", 98) 36 | /// // Stream 2 received: ("Second", 98) 37 | /// // Stream 1 received: ("Second", 98) 38 | /// // AsyncSequence produces: ("Third", 61) 39 | /// // Stream 2 received: ("Third", 61) 40 | /// // Stream 1 received: ("Third", 61) 41 | /// ``` 42 | /// In this example, the output shows that the upstream async sequence produces each random value only one time, 43 | /// and then sends the value to both client loops. 44 | /// 45 | /// Without the ``share()`` operator, loop 1 receives three random values, 46 | /// followed by loop 2 receiving three different random values. 47 | /// 48 | /// - Returns: A class instance that shares elements received from its upstream async sequence to multiple client iterations. 49 | func share() -> AsyncShareSequence where Self.AsyncIterator: Sendable, Element: Sendable { 50 | let subject = AsyncThrowingPassthroughSubject() 51 | return self.multicast(subject).autoconnect() 52 | } 53 | } 54 | 55 | public typealias AsyncShareSequence = AsyncMulticastSequence> 56 | -------------------------------------------------------------------------------- /Sources/Supporting/ManagedCriticalState.swift: -------------------------------------------------------------------------------- 1 | import Darwin 2 | 3 | final class LockedBuffer: ManagedBuffer { 4 | deinit { 5 | _ = self.withUnsafeMutablePointerToElements { lock in 6 | lock.deinitialize(count: 1) 7 | } 8 | } 9 | } 10 | 11 | struct ManagedCriticalState { 12 | let buffer: ManagedBuffer 13 | 14 | init(_ initial: State) { 15 | buffer = LockedBuffer.create(minimumCapacity: 1) { buffer in 16 | buffer.withUnsafeMutablePointerToElements { lock in 17 | lock.initialize(to: os_unfair_lock()) 18 | } 19 | return initial 20 | } 21 | } 22 | 23 | @discardableResult 24 | func withCriticalRegion( 25 | _ critical: (inout State) throws -> R 26 | ) rethrows -> R { 27 | try buffer.withUnsafeMutablePointers { header, lock in 28 | os_unfair_lock_lock(lock) 29 | defer { os_unfair_lock_unlock(lock) } 30 | return try critical(&header.pointee) 31 | } 32 | } 33 | 34 | func apply(criticalState newState: State) { 35 | self.withCriticalRegion { actual in 36 | actual = newState 37 | } 38 | } 39 | 40 | var criticalState: State { 41 | self.withCriticalRegion { $0 } 42 | } 43 | } 44 | 45 | extension ManagedCriticalState: @unchecked Sendable where State: Sendable { } 46 | -------------------------------------------------------------------------------- /Sources/Supporting/Regulator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Regulator.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 08/09/2022. 6 | // 7 | 8 | enum RegulatedElement: @unchecked Sendable { 9 | case termination 10 | case element(result: Result) 11 | } 12 | 13 | final class Regulator: @unchecked Sendable { 14 | enum State { 15 | case idle 16 | case suspended(UnsafeContinuation) 17 | case active 18 | case finished 19 | } 20 | 21 | let base: Base 22 | let state: ManagedCriticalState 23 | let onNextRegulatedElement: @Sendable (RegulatedElement) -> Void 24 | 25 | init( 26 | _ base: Base, 27 | onNextRegulatedElement: @Sendable @escaping (RegulatedElement) -> Void 28 | ) { 29 | self.base = base 30 | self.state = ManagedCriticalState(.idle) 31 | self.onNextRegulatedElement = onNextRegulatedElement 32 | } 33 | 34 | func unsuspendAndExitOnCancel() { 35 | let continuation = state.withCriticalRegion { state -> UnsafeContinuation? in 36 | switch state { 37 | case .suspended(let continuation): 38 | state = .finished 39 | return continuation 40 | default: 41 | state = .finished 42 | return nil 43 | } 44 | } 45 | 46 | continuation?.resume(returning: true) 47 | } 48 | 49 | func iterate() async { 50 | await withTaskCancellationHandler { 51 | self.unsuspendAndExitOnCancel() 52 | } operation: { 53 | var mutableBase = base.makeAsyncIterator() 54 | 55 | do { 56 | baseLoop: while true { 57 | let shouldExit = await withUnsafeContinuation { (continuation: UnsafeContinuation) in 58 | let decision = self.state.withCriticalRegion { state -> (UnsafeContinuation?, Bool) in 59 | switch state { 60 | case .idle: 61 | state = .suspended(continuation) 62 | return (nil, false) 63 | case .suspended(let continuation): 64 | assertionFailure("Inconsistent state, the base is already suspended") 65 | return (continuation, true) 66 | case .active: 67 | return (continuation, false) 68 | case .finished: 69 | return (continuation, true) 70 | } 71 | } 72 | 73 | decision.0?.resume(returning: decision.1) 74 | } 75 | 76 | if shouldExit { 77 | // end the loop ... no more values from this base 78 | break baseLoop 79 | } 80 | 81 | let element = try await mutableBase.next() 82 | 83 | let regulatedElement = self.state.withCriticalRegion { state -> RegulatedElement in 84 | switch element { 85 | case .some(let element): 86 | state = .idle 87 | return .element(result: .success(element)) 88 | case .none: 89 | state = .finished 90 | return .termination 91 | } 92 | } 93 | 94 | self.onNextRegulatedElement(regulatedElement) 95 | } 96 | } catch { 97 | self.state.withCriticalRegion { state in 98 | state = .finished 99 | } 100 | self.onNextRegulatedElement(.element(result: .failure(error))) 101 | } 102 | } 103 | } 104 | 105 | @Sendable 106 | func requestNextRegulatedElement() { 107 | let continuation = self.state.withCriticalRegion { state -> UnsafeContinuation? in 108 | switch state { 109 | case .suspended(let continuation): 110 | state = .active 111 | return continuation 112 | case .idle: 113 | state = .active 114 | return nil 115 | case .active, .finished: 116 | return nil 117 | } 118 | } 119 | 120 | continuation?.resume(returning: false) 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Sources/Supporting/Result+ErrorMechanism.swift: -------------------------------------------------------------------------------- 1 | // ===----------------------------------------------------------------------===// 2 | // 3 | // This source file is part of the Swift Async Algorithms open source project 4 | // 5 | // Copyright (c) 2022 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | // ===----------------------------------------------------------------------===// 11 | 12 | // This is a hack around the fact that we don't have generic effects 13 | // alternatively in the use cases we would want `rethrows(unsafe)` 14 | // or something like that to avoid this nifty hack... 15 | 16 | @rethrows 17 | internal protocol _ErrorMechanism { 18 | associatedtype Output 19 | func get() throws -> Output 20 | } 21 | 22 | extension _ErrorMechanism { 23 | // rethrow an error only in the cases where it is known to be reachable 24 | internal func _rethrowError() rethrows -> Never { 25 | _ = try _rethrowGet() 26 | fatalError("materialized error without being in a throwing context") 27 | } 28 | 29 | internal func _rethrowGet() rethrows -> Output { 30 | return try get() 31 | } 32 | } 33 | 34 | extension Result: _ErrorMechanism { } 35 | -------------------------------------------------------------------------------- /Tests/AsyncChannels/AsyncBufferedChannelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncBufferedChannelTests.swift 3 | // 4 | // 5 | // Created by Thibault WITTEMBERG on 06/08/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncBufferedChannelTests: XCTestCase { 12 | func test_send_queues_elements_and_delivers_to_one_consumer() async { 13 | let expected = [1, 2, 3, 4, 5] 14 | 15 | // Given 16 | let sut = AsyncBufferedChannel() 17 | 18 | // When 19 | sut.send(1) 20 | sut.send(2) 21 | sut.send(3) 22 | sut.send(4) 23 | sut.send(5) 24 | sut.finish() 25 | 26 | var received = [Int]() 27 | for await element in sut { 28 | received.append(element) 29 | } 30 | 31 | // Then 32 | XCTAssertEqual(received, expected) 33 | } 34 | 35 | func test_send_resumes_awaiting() async { 36 | let iterationIsAwaiting = expectation(description: "Iteration is awaiting") 37 | let expected = 1 38 | 39 | // Given 40 | let sut = AsyncBufferedChannel() 41 | 42 | let task = Task { 43 | let received = await sut.next { 44 | iterationIsAwaiting.fulfill() 45 | } 46 | return received 47 | } 48 | 49 | wait(for: [iterationIsAwaiting], timeout: 1.0) 50 | 51 | // When 52 | sut.send(1) 53 | 54 | let received = await task.value 55 | 56 | // Then 57 | XCTAssertEqual(received, expected) 58 | } 59 | 60 | func test_send_from_several_producers_queues_elements_and_delivers_to_several_consumers() async { 61 | let expected1 = Set((1...24)) 62 | let expected2 = Set((25...50)) 63 | let expected = expected1.union(expected2) 64 | 65 | // Given 66 | let sut = AsyncBufferedChannel() 67 | 68 | // When 69 | let sendTask1 = Task { 70 | for i in expected1 { 71 | sut.send(i) 72 | } 73 | } 74 | 75 | let sendTask2 = Task { 76 | for i in expected2 { 77 | sut.send(i) 78 | } 79 | } 80 | 81 | await sendTask1.value 82 | await sendTask2.value 83 | 84 | sut.finish() 85 | 86 | let task1 = Task<[Int], Never> { 87 | var received = [Int]() 88 | for await element in sut { 89 | received.append(element) 90 | } 91 | return received 92 | } 93 | 94 | let task2 = Task<[Int], Never> { 95 | var received = [Int]() 96 | for await element in sut { 97 | received.append(element) 98 | } 99 | return received 100 | } 101 | 102 | let received1 = await task1.value 103 | let received2 = await task2.value 104 | 105 | let received: Set = Set(received1).union(received2) 106 | 107 | // Then 108 | XCTAssertEqual(received, expected) 109 | } 110 | 111 | func test_asyncBufferedChannel_ends_iteration_when_task_is_cancelled() async { 112 | let taskCanBeCancelled = expectation(description: "The can be cancelled") 113 | let taskWasCancelled = expectation(description: "The task was cancelled") 114 | let iterationHasFinished = expectation(description: "The iteration we finished") 115 | 116 | let sut = AsyncBufferedChannel() 117 | sut.send(1) 118 | sut.send(2) 119 | sut.send(3) 120 | sut.send(4) 121 | sut.send(5) 122 | 123 | let task = Task { 124 | var received: Int? 125 | for await element in sut { 126 | received = element 127 | taskCanBeCancelled.fulfill() 128 | wait(for: [taskWasCancelled], timeout: 1.0) 129 | } 130 | iterationHasFinished.fulfill() 131 | return received 132 | } 133 | 134 | wait(for: [taskCanBeCancelled], timeout: 1.0) 135 | 136 | // When 137 | task.cancel() 138 | taskWasCancelled.fulfill() 139 | 140 | wait(for: [iterationHasFinished], timeout: 1.0) 141 | 142 | // Then 143 | let received = await task.value 144 | XCTAssertEqual(received, 1) 145 | } 146 | 147 | func test_finished_ends_awaiting_consumers_and_immediately_resumes_pastEnd() async throws { 148 | let iteration1IsAwaiting = expectation(description: "") 149 | let iteration1IsFinished = expectation(description: "") 150 | 151 | let iteration2IsAwaiting = expectation(description: "") 152 | let iteration2IsFinished = expectation(description: "") 153 | 154 | // Given 155 | let sut = AsyncBufferedChannel() 156 | 157 | let task1 = Task { 158 | let received = await sut.next { 159 | iteration1IsAwaiting.fulfill() 160 | } 161 | iteration1IsFinished.fulfill() 162 | return received 163 | } 164 | 165 | let task2 = Task { 166 | let received = await sut.next { 167 | iteration2IsAwaiting.fulfill() 168 | } 169 | iteration2IsFinished.fulfill() 170 | return received 171 | } 172 | 173 | wait(for: [iteration1IsAwaiting, iteration2IsAwaiting], timeout: 1.0) 174 | 175 | // When 176 | sut.finish() 177 | 178 | wait(for: [iteration1IsFinished, iteration2IsFinished], timeout: 1.0) 179 | 180 | let received1 = await task1.value 181 | let received2 = await task2.value 182 | 183 | XCTAssertNil(received1) 184 | XCTAssertNil(received2) 185 | 186 | let iterator = sut.makeAsyncIterator() 187 | let received = await iterator.next() 188 | XCTAssertNil(received) 189 | } 190 | 191 | func test_send_does_not_queue_when_already_finished() async { 192 | // Given 193 | let sut = AsyncBufferedChannel() 194 | 195 | // When 196 | sut.finish() 197 | sut.send(1) 198 | 199 | // Then 200 | let iterator = sut.makeAsyncIterator() 201 | let received = await iterator.next() 202 | 203 | XCTAssertNil(received) 204 | } 205 | 206 | func test_cancellation_immediately_resumes_when_already_finished() async { 207 | let iterationIsFinished = expectation(description: "The task was cancelled") 208 | 209 | // Given 210 | let sut = AsyncBufferedChannel() 211 | sut.finish() 212 | 213 | // When 214 | Task { 215 | for await _ in sut {} 216 | iterationIsFinished.fulfill() 217 | }.cancel() 218 | 219 | // Then 220 | wait(for: [iterationIsFinished], timeout: 1.0) 221 | } 222 | 223 | func test_awaiting_uses_id_for_equatable() { 224 | // Given 225 | let awaiting1 = AsyncBufferedChannel.Awaiting.placeHolder(id: 1) 226 | let awaiting2 = AsyncBufferedChannel.Awaiting.placeHolder(id: 2) 227 | let awaiting3 = AsyncBufferedChannel.Awaiting.placeHolder(id: 1) 228 | 229 | // When 230 | // Then 231 | XCTAssertEqual(awaiting1, awaiting3) 232 | XCTAssertNotEqual(awaiting1, awaiting2) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /Tests/AsyncSubjets/AsyncCurrentValueSubjectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncCurrentValueSubjectTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 10/01/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncCurrentValueSubjectTests: XCTestCase { 12 | func test_init_stores_element() async { 13 | let value = Int.random(in: 0...100) 14 | let sut = AsyncCurrentValueSubject(value) 15 | let element = sut.value 16 | XCTAssertEqual(value, element) 17 | } 18 | 19 | func test_subject_replays_element_when_new_iterator() async { 20 | let expectedResult = 1 21 | 22 | let sut = AsyncCurrentValueSubject(expectedResult) 23 | 24 | var iterator1 = sut.makeAsyncIterator() 25 | var iterator2 = sut.makeAsyncIterator() 26 | 27 | let received1 = await iterator1.next() 28 | let received2 = await iterator2.next() 29 | 30 | XCTAssertEqual(received1, 1) 31 | XCTAssertEqual(received2, 1) 32 | } 33 | 34 | func test_send_pushes_values_in_the_subject() { 35 | let hasReceivedOneElementExpectation = expectation(description: "One element has been iterated in the async sequence") 36 | hasReceivedOneElementExpectation.expectedFulfillmentCount = 2 37 | 38 | let hasReceivedSentElementsExpectation = expectation(description: "Send pushes elements in created AsyncSequences") 39 | hasReceivedSentElementsExpectation.expectedFulfillmentCount = 2 40 | 41 | let expectedResult = [1, 2, 3] 42 | 43 | let sut = AsyncCurrentValueSubject(1) 44 | 45 | Task { 46 | var receivedElements = [Int]() 47 | 48 | for await element in sut { 49 | if element == 1 { 50 | hasReceivedOneElementExpectation.fulfill() 51 | } 52 | receivedElements.append(element) 53 | if element == 3 { 54 | XCTAssertEqual(receivedElements, expectedResult) 55 | hasReceivedSentElementsExpectation.fulfill() 56 | } 57 | } 58 | } 59 | 60 | Task { 61 | var receivedElements = [Int]() 62 | 63 | for await element in sut { 64 | if element == 1 { 65 | hasReceivedOneElementExpectation.fulfill() 66 | } 67 | receivedElements.append(element) 68 | if element == 3 { 69 | XCTAssertEqual(receivedElements, expectedResult) 70 | hasReceivedSentElementsExpectation.fulfill() 71 | } 72 | } 73 | } 74 | 75 | wait(for: [hasReceivedOneElementExpectation], timeout: 1) 76 | 77 | sut.send(2) 78 | sut.value = 3 79 | 80 | wait(for: [hasReceivedSentElementsExpectation], timeout: 1) 81 | } 82 | 83 | func test_sendFinished_ends_the_subject_and_immediately_resumes_futur_consumer() async { 84 | let hasReceivedOneElementExpectation = expectation(description: "One element has been iterated in the async sequence") 85 | hasReceivedOneElementExpectation.expectedFulfillmentCount = 2 86 | 87 | let hasFinishedExpectation = expectation(description: "Send(.finished) finishes all created AsyncSequences") 88 | hasFinishedExpectation.expectedFulfillmentCount = 2 89 | 90 | let sut = AsyncCurrentValueSubject(1) 91 | 92 | Task { 93 | for await element in sut { 94 | if element == 1 { 95 | hasReceivedOneElementExpectation.fulfill() 96 | } 97 | } 98 | hasFinishedExpectation.fulfill() 99 | } 100 | 101 | Task { 102 | for await element in sut { 103 | if element == 1 { 104 | hasReceivedOneElementExpectation.fulfill() 105 | } 106 | } 107 | hasFinishedExpectation.fulfill() 108 | } 109 | 110 | wait(for: [hasReceivedOneElementExpectation], timeout: 1) 111 | 112 | sut.send(.finished) 113 | 114 | wait(for: [hasFinishedExpectation], timeout: 1) 115 | 116 | var iterator = sut.makeAsyncIterator() 117 | let received = await iterator.next() 118 | XCTAssertNil(received) 119 | } 120 | 121 | func test_subject_finishes_when_task_is_cancelled() { 122 | let canCancelExpectation = expectation(description: "The first element has been emitted") 123 | let hasCancelExceptation = expectation(description: "The task has been cancelled") 124 | let taskHasFinishedExpectation = expectation(description: "The task has finished") 125 | 126 | let sut = AsyncCurrentValueSubject(1) 127 | 128 | let task = Task { 129 | var firstElement: Int? 130 | for await element in sut { 131 | firstElement = element 132 | canCancelExpectation.fulfill() 133 | wait(for: [hasCancelExceptation], timeout: 5) 134 | } 135 | XCTAssertEqual(firstElement, 1) 136 | taskHasFinishedExpectation.fulfill() 137 | } 138 | 139 | wait(for: [canCancelExpectation], timeout: 5) // one element has been emitted, we can cancel the task 140 | 141 | task.cancel() 142 | 143 | hasCancelExceptation.fulfill() // we can release the lock in the for loop 144 | 145 | wait(for: [taskHasFinishedExpectation], timeout: 5) // task has been cancelled and has finished 146 | } 147 | 148 | func test_subject_handles_concurrency() async throws { 149 | let canSendExpectation = expectation(description: "CurrentValue is ready to be sent values") 150 | canSendExpectation.expectedFulfillmentCount = 2 151 | 152 | let expectedElements = (0...2000).map { $0 } 153 | 154 | let sut = AsyncCurrentValueSubject(0) 155 | 156 | // concurrently iterate the sut 1 157 | let taskA = Task { () -> [Int] in 158 | var received = [Int]() 159 | var iterator = sut.makeAsyncIterator() 160 | canSendExpectation.fulfill() 161 | while let element = await iterator.next() { 162 | received.append(element) 163 | } 164 | return received.sorted() 165 | } 166 | 167 | // concurrently iterate the sut 2 168 | let taskB = Task { () -> [Int] in 169 | var received = [Int]() 170 | var iterator = sut.makeAsyncIterator() 171 | canSendExpectation.fulfill() 172 | while let element = await iterator.next() { 173 | received.append(element) 174 | } 175 | return received.sorted() 176 | } 177 | 178 | await waitForExpectations(timeout: 1) 179 | 180 | // concurrently push values in the sut 1 181 | let task1 = Task { 182 | for index in (1...1000) { 183 | sut.send(index) 184 | } 185 | } 186 | 187 | // concurrently push values in the sut 2 188 | let task2 = Task { 189 | for index in (1001...2000) { 190 | sut.send(index) 191 | } 192 | } 193 | 194 | await task1.value 195 | await task2.value 196 | 197 | Task { 198 | sut.send(.finished) 199 | } 200 | 201 | let receivedElementsA = await taskA.value 202 | let receivedElementsB = await taskB.value 203 | 204 | XCTAssertEqual(receivedElementsA, expectedElements) 205 | XCTAssertEqual(receivedElementsB, expectedElements) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /Tests/AsyncSubjets/AsyncPassthroughSubjectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncPassthroughSubjectTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 10/01/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncPassthroughSubjectTests: XCTestCase { 12 | func test_send_pushes_elements_in_the_subject() { 13 | let isReadyToBeIteratedExpectation = expectation(description: "Passthrough subject iterators are ready for iteration") 14 | isReadyToBeIteratedExpectation.expectedFulfillmentCount = 2 15 | 16 | let hasReceivedSentElementsExpectation = expectation(description: "Send pushes elements in created AsyncSequences") 17 | hasReceivedSentElementsExpectation.expectedFulfillmentCount = 2 18 | 19 | let expectedResult = [1, 2, 3] 20 | 21 | let sut = AsyncPassthroughSubject() 22 | 23 | Task { 24 | var receivedElements = [Int]() 25 | 26 | var it = sut.makeAsyncIterator() 27 | isReadyToBeIteratedExpectation.fulfill() 28 | while let element = await it.next() { 29 | receivedElements.append(element) 30 | if element == 3 { 31 | XCTAssertEqual(receivedElements, expectedResult) 32 | hasReceivedSentElementsExpectation.fulfill() 33 | } 34 | } 35 | } 36 | 37 | Task { 38 | var receivedElements = [Int]() 39 | 40 | var it = sut.makeAsyncIterator() 41 | isReadyToBeIteratedExpectation.fulfill() 42 | while let element = await it.next() { 43 | receivedElements.append(element) 44 | if element == 3 { 45 | XCTAssertEqual(receivedElements, expectedResult) 46 | hasReceivedSentElementsExpectation.fulfill() 47 | } 48 | } 49 | } 50 | 51 | wait(for: [isReadyToBeIteratedExpectation], timeout: 1) 52 | 53 | sut.send(1) 54 | sut.send(2) 55 | sut.send(3) 56 | 57 | wait(for: [hasReceivedSentElementsExpectation], timeout: 1) 58 | } 59 | 60 | func test_sendFinished_ends_the_subject_and_immediately_resumes_futur_consumer() async { 61 | let isReadyToBeIteratedExpectation = expectation(description: "Passthrough subject iterators are ready for iteration") 62 | isReadyToBeIteratedExpectation.expectedFulfillmentCount = 2 63 | 64 | let hasReceivedOneElementExpectation = expectation(description: "One element has been iterated in the async sequence") 65 | hasReceivedOneElementExpectation.expectedFulfillmentCount = 2 66 | 67 | let hasFinishedExpectation = expectation(description: "Send(.finished) finishes all created AsyncSequences") 68 | hasFinishedExpectation.expectedFulfillmentCount = 2 69 | 70 | let sut = AsyncPassthroughSubject() 71 | 72 | Task { 73 | var it = sut.makeAsyncIterator() 74 | isReadyToBeIteratedExpectation.fulfill() 75 | while let element = await it.next() { 76 | if element == 1 { 77 | hasReceivedOneElementExpectation.fulfill() 78 | } 79 | } 80 | hasFinishedExpectation.fulfill() 81 | } 82 | 83 | Task { 84 | var it = sut.makeAsyncIterator() 85 | isReadyToBeIteratedExpectation.fulfill() 86 | while let element = await it.next() { 87 | if element == 1 { 88 | hasReceivedOneElementExpectation.fulfill() 89 | } 90 | } 91 | hasFinishedExpectation.fulfill() 92 | } 93 | 94 | wait(for: [isReadyToBeIteratedExpectation], timeout: 1) 95 | 96 | sut.send(1) 97 | 98 | wait(for: [hasReceivedOneElementExpectation], timeout: 1) 99 | 100 | sut.send( .finished) 101 | 102 | wait(for: [hasFinishedExpectation], timeout: 1) 103 | 104 | var iterator = sut.makeAsyncIterator() 105 | let received = await iterator.next() 106 | XCTAssertNil(received) 107 | } 108 | 109 | func test_subject_finishes_when_task_is_cancelled() { 110 | let isReadyToBeIteratedExpectation = expectation(description: "Passthrough subject iterators are ready for iteration") 111 | let canCancelExpectation = expectation(description: "The first element has been emitted") 112 | let hasCancelExpectation = expectation(description: "The task has been cancelled") 113 | let taskHasFinishedExpectation = expectation(description: "The task has finished") 114 | 115 | let sut = AsyncPassthroughSubject() 116 | 117 | let task = Task { 118 | var receivedElements = [Int]() 119 | 120 | var it = sut.makeAsyncIterator() 121 | isReadyToBeIteratedExpectation.fulfill() 122 | while let element = await it.next() { 123 | receivedElements.append(element) 124 | canCancelExpectation.fulfill() 125 | wait(for: [hasCancelExpectation], timeout: 5) 126 | } 127 | XCTAssertEqual(receivedElements, [1]) 128 | taskHasFinishedExpectation.fulfill() 129 | } 130 | 131 | wait(for: [isReadyToBeIteratedExpectation], timeout: 1) 132 | 133 | sut.send(1) 134 | 135 | wait(for: [canCancelExpectation], timeout: 5) // one element has been emitted, we can cancel the task 136 | 137 | task.cancel() 138 | 139 | hasCancelExpectation.fulfill() // we can release the lock in the for loop 140 | 141 | wait(for: [taskHasFinishedExpectation], timeout: 5) // task has been cancelled and has finished 142 | } 143 | 144 | func test_subject_handles_concurrency() async { 145 | let canSendExpectation = expectation(description: "Passthrough is ready to be sent values") 146 | canSendExpectation.expectedFulfillmentCount = 2 147 | 148 | let expectedElements = (0...2000).map { $0 } 149 | 150 | let sut = AsyncPassthroughSubject() 151 | 152 | // concurrently iterate the sut 1 153 | let taskA = Task<[Int], Never> { 154 | var received = [Int]() 155 | var iterator = sut.makeAsyncIterator() 156 | canSendExpectation.fulfill() 157 | while let element = await iterator.next() { 158 | received.append(element) 159 | } 160 | return received.sorted() 161 | } 162 | 163 | // concurrently iterate the sut 2 164 | let taskB = Task<[Int], Never> { 165 | var received = [Int]() 166 | var iterator = sut.makeAsyncIterator() 167 | canSendExpectation.fulfill() 168 | while let element = await iterator.next() { 169 | received.append(element) 170 | } 171 | return received.sorted() 172 | } 173 | 174 | await waitForExpectations(timeout: 1) 175 | 176 | // concurrently push values in the sut 1 177 | let task1 = Task { 178 | for index in (0...1000) { 179 | sut.send(index) 180 | } 181 | } 182 | 183 | // concurrently push values in the sut 2 184 | let task2 = Task { 185 | for index in (1001...2000) { 186 | sut.send(index) 187 | } 188 | } 189 | 190 | await task1.value 191 | await task2.value 192 | 193 | sut.send(.finished) 194 | 195 | let receivedElementsA = await taskA.value 196 | let receivedElementsB = await taskB.value 197 | 198 | XCTAssertEqual(receivedElementsA, expectedElements) 199 | XCTAssertEqual(receivedElementsB, expectedElements) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /Tests/AsyncSubjets/AsyncReplaySubjectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncReplaySubjectTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 02/01/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncReplaySubjectTests: XCTestCase { 12 | func test_send_replays_buffered_elements() { 13 | let exp = expectation(description: "Send has stacked elements in the replay the buffer") 14 | exp.expectedFulfillmentCount = 2 15 | 16 | let expectedResult = [2, 3, 4, 5, 6] 17 | 18 | let sut = AsyncReplaySubject(bufferSize: 5) 19 | sut.send(1) 20 | sut.send(2) 21 | sut.send(3) 22 | sut.send(4) 23 | sut.send(5) 24 | sut.send(6) 25 | 26 | Task { 27 | var receivedElements = [Int]() 28 | 29 | for await element in sut { 30 | receivedElements.append(element) 31 | if element == 6 { 32 | XCTAssertEqual(receivedElements, expectedResult) 33 | exp.fulfill() 34 | } 35 | } 36 | } 37 | 38 | Task { 39 | var receivedElements = [Int]() 40 | 41 | for await element in sut { 42 | receivedElements.append(element) 43 | if element == 6 { 44 | XCTAssertEqual(receivedElements, expectedResult) 45 | exp.fulfill() 46 | } 47 | } 48 | } 49 | 50 | waitForExpectations(timeout: 0.5) 51 | } 52 | 53 | func test_send_pushes_elements_in_the_subject() { 54 | let hasReceivedOneElementExpectation = expectation(description: "One element has been iterated in the async sequence") 55 | hasReceivedOneElementExpectation.expectedFulfillmentCount = 2 56 | 57 | let hasReceivedSentElementsExpectation = expectation(description: "Send pushes elements in created AsyncSequences") 58 | hasReceivedSentElementsExpectation.expectedFulfillmentCount = 2 59 | 60 | let expectedResult = [1, 2, 3] 61 | 62 | let sut = AsyncReplaySubject(bufferSize: 5) 63 | 64 | sut.send(1) 65 | 66 | Task { 67 | var receivedElements = [Int]() 68 | 69 | for await element in sut { 70 | if element == 1 { 71 | hasReceivedOneElementExpectation.fulfill() 72 | } 73 | receivedElements.append(element) 74 | if element == 3 { 75 | XCTAssertEqual(receivedElements, expectedResult) 76 | hasReceivedSentElementsExpectation.fulfill() 77 | } 78 | } 79 | } 80 | 81 | Task { 82 | var receivedElements = [Int]() 83 | 84 | for await element in sut { 85 | if element == 1 { 86 | hasReceivedOneElementExpectation.fulfill() 87 | } 88 | receivedElements.append(element) 89 | if element == 3 { 90 | XCTAssertEqual(receivedElements, expectedResult) 91 | hasReceivedSentElementsExpectation.fulfill() 92 | } 93 | } 94 | } 95 | 96 | wait(for: [hasReceivedOneElementExpectation], timeout: 1) 97 | 98 | sut.send(2) 99 | sut.send(3) 100 | 101 | wait(for: [hasReceivedSentElementsExpectation], timeout: 1) 102 | } 103 | 104 | func test_sendFinished_ends_the_subject_and_immediately_resumes_futur_consumer() async { 105 | let hasReceivedOneElementExpectation = expectation(description: "One element has been iterated in the async sequence") 106 | hasReceivedOneElementExpectation.expectedFulfillmentCount = 2 107 | 108 | let hasFinishedExpectation = expectation(description: "Send(.finished) finishes all created AsyncSequences") 109 | hasFinishedExpectation.expectedFulfillmentCount = 2 110 | 111 | let sut = AsyncReplaySubject(bufferSize: 1) 112 | 113 | sut.send(1) 114 | 115 | Task { 116 | for await element in sut { 117 | if element == 1 { 118 | hasReceivedOneElementExpectation.fulfill() 119 | } 120 | } 121 | hasFinishedExpectation.fulfill() 122 | } 123 | 124 | Task { 125 | for await element in sut { 126 | if element == 1 { 127 | hasReceivedOneElementExpectation.fulfill() 128 | } 129 | } 130 | hasFinishedExpectation.fulfill() 131 | } 132 | 133 | wait(for: [hasReceivedOneElementExpectation], timeout: 1) 134 | 135 | sut.send(.finished) 136 | 137 | wait(for: [hasFinishedExpectation], timeout: 1) 138 | 139 | var iterator = sut.makeAsyncIterator() 140 | let received = await iterator.next() 141 | XCTAssertNil(received) 142 | } 143 | 144 | func test_subject_finishes_when_task_is_cancelled() { 145 | let canCancelExpectation = expectation(description: "The first element has been emitted") 146 | let hasCancelExceptation = expectation(description: "The task has been cancelled") 147 | let taskHasFinishedExpectation = expectation(description: "The task has finished") 148 | 149 | let sut = AsyncReplaySubject(bufferSize: 1) 150 | 151 | sut.send(1) 152 | 153 | let task = Task { 154 | var firstElement: Int? 155 | for await element in sut { 156 | firstElement = element 157 | canCancelExpectation.fulfill() 158 | wait(for: [hasCancelExceptation], timeout: 5) 159 | } 160 | XCTAssertEqual(firstElement, 1) 161 | taskHasFinishedExpectation.fulfill() 162 | } 163 | 164 | wait(for: [canCancelExpectation], timeout: 5) // one element has been emitted, we can cancel the task 165 | 166 | task.cancel() 167 | 168 | hasCancelExceptation.fulfill() // we can release the lock in the for loop 169 | 170 | wait(for: [taskHasFinishedExpectation], timeout: 5) // task has been cancelled and has finished 171 | } 172 | 173 | func test_subject_handles_concurrency() async { 174 | let canSendExpectation = expectation(description: "Replay is ready to be sent values") 175 | canSendExpectation.expectedFulfillmentCount = 2 176 | 177 | let expectedElements = (0...2000).map { $0 } 178 | 179 | let sut = AsyncReplaySubject(bufferSize: 0) 180 | 181 | // concurrently iterate the sut 1 182 | let taskA = Task { () -> [Int] in 183 | var received = [Int]() 184 | var iterator = sut.makeAsyncIterator() 185 | canSendExpectation.fulfill() 186 | while let element = await iterator.next() { 187 | received.append(element) 188 | } 189 | return received.sorted() 190 | } 191 | 192 | // concurrently iterate the sut 2 193 | let taskB = Task { () -> [Int] in 194 | var received = [Int]() 195 | var iterator = sut.makeAsyncIterator() 196 | canSendExpectation.fulfill() 197 | while let element = await iterator.next() { 198 | received.append(element) 199 | } 200 | return received.sorted() 201 | } 202 | 203 | await waitForExpectations(timeout: 1) 204 | 205 | // concurrently push values in the sut 1 206 | let task1 = Task { 207 | for index in (0...1000) { 208 | sut.send(index) 209 | } 210 | } 211 | 212 | // concurrently push values in the sut 2 213 | let task2 = Task { 214 | for index in (1001...2000) { 215 | sut.send(index) 216 | } 217 | } 218 | 219 | await task1.value 220 | await task2.value 221 | 222 | sut.send(.finished) 223 | 224 | let receivedElementsA = await taskA.value 225 | let receivedElementsB = await taskB.value 226 | 227 | XCTAssertEqual(receivedElementsA, expectedElements) 228 | XCTAssertEqual(receivedElementsB, expectedElements) 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /Tests/AsyncSubjets/StreamedTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StreamedTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 20/03/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import Combine 10 | import XCTest 11 | 12 | final class StreamedTests: XCTestCase { 13 | @Streamed 14 | var sut: Int = 0 15 | 16 | func test_streamed_gets_and_sets_element() { 17 | XCTAssertEqual(sut, 0) 18 | let newValue = Int.random(in: 0...100) 19 | self.sut = newValue 20 | XCTAssertEqual(sut, newValue) 21 | } 22 | 23 | func test_streamed_projects_in_asyncSequence() { 24 | let firstElementIsReceivedExpectation = expectation(description: "The first element has been received") 25 | let fifthElementIsReceivedExpectation = expectation(description: "The fifth element has been received") 26 | 27 | let expectedElements = [0, 1, 2, 3, 4] 28 | let task = Task(priority: .high) { 29 | var receivedElements = [Int]() 30 | for try await element in self.$sut { 31 | receivedElements.append(element) 32 | 33 | if element == 0 { 34 | firstElementIsReceivedExpectation.fulfill() 35 | } 36 | if element == 4 { 37 | fifthElementIsReceivedExpectation.fulfill() 38 | XCTAssertEqual(receivedElements, expectedElements) 39 | } 40 | } 41 | } 42 | 43 | wait(for: [firstElementIsReceivedExpectation], timeout: 1) 44 | 45 | sut = 1 46 | sut = 2 47 | sut = 3 48 | sut = 4 49 | 50 | wait(for: [fifthElementIsReceivedExpectation], timeout: 1) 51 | task.cancel() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Tests/Combiners/WithLatestFrom/AsyncWithLatestFrom2SequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncWithLatestFrom2SequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 11/09/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncWithLatestFrom2SequenceTests: XCTestCase { 12 | func test_withLatestFrom_uses_latest_element_from_others() async { 13 | // Timeline 14 | // base: -0-----1 ---2 ---3 ---4 ---5 -x--| 15 | // other1: ---a---- ---- -b-- -x-- ---- ----| 16 | // other2: -----x-- -y-- ---- ---- -x-- ----| 17 | // expected: -------(1,a,x)---(2,a,y)---(3,b,y)---(4,b,y)---(5,b,y)-x--| 18 | let baseHasProduced0 = expectation(description: "Base has produced 0") 19 | 20 | let other1HasProducedA = expectation(description: "Other has produced 'a'") 21 | let other1HasProducedB = expectation(description: "Other has produced 'b'") 22 | 23 | let other2HasProducedX = expectation(description: "Other has produced 'x'") 24 | let other2HasProducedY = expectation(description: "Other has produced 'y'") 25 | 26 | let base = AsyncBufferedChannel() 27 | let other1 = AsyncBufferedChannel() 28 | let other2 = AsyncBufferedChannel() 29 | 30 | var sequence = base.withLatest(from: other1, other2) 31 | // expectations that ensure that "others" has really delivered 32 | // their elements before requesting the next element from "base" 33 | sequence.onOther1Element = { @Sendable element in 34 | if element == "a" { 35 | other1HasProducedA.fulfill() 36 | } 37 | 38 | if element == "b" { 39 | other1HasProducedB.fulfill() 40 | } 41 | } 42 | 43 | sequence.onOther2Element = { @Sendable element in 44 | if element == "x" { 45 | other2HasProducedX.fulfill() 46 | } 47 | 48 | if element == "y" { 49 | other2HasProducedY.fulfill() 50 | } 51 | } 52 | 53 | sequence.onBaseElement = { @Sendable element in 54 | if element == 0 { 55 | baseHasProduced0.fulfill() 56 | } 57 | } 58 | 59 | var iterator = sequence.makeAsyncIterator() 60 | 61 | Task { 62 | base.send(0) 63 | wait(for: [baseHasProduced0], timeout: 1.0) 64 | other1.send("a") 65 | wait(for: [other1HasProducedA], timeout: 1.0) 66 | other2.send("x") 67 | wait(for: [other2HasProducedX], timeout: 1.0) 68 | base.send(1) 69 | } 70 | 71 | let element1 = await iterator.next() 72 | XCTAssertEqual(Tuple3(element1!), Tuple3((1, "a", "x"))) 73 | 74 | Task { 75 | other2.send("y") 76 | wait(for: [other2HasProducedY], timeout: 1.0) 77 | base.send(2) 78 | } 79 | 80 | let element2 = await iterator.next() 81 | XCTAssertEqual(Tuple3(element2!), Tuple3((2, "a", "y"))) 82 | 83 | Task { 84 | other1.send("b") 85 | wait(for: [other1HasProducedB], timeout: 1.0) 86 | base.send(3) 87 | } 88 | 89 | let element3 = await iterator.next() 90 | XCTAssertEqual(Tuple3(element3!), Tuple3((3, "b", "y"))) 91 | 92 | Task { 93 | other1.finish() 94 | base.send(4) 95 | } 96 | 97 | let element4 = await iterator.next() 98 | XCTAssertEqual(Tuple3(element4!), Tuple3((4, "b", "y"))) 99 | 100 | Task { 101 | other2.finish() 102 | base.send(5) 103 | } 104 | 105 | let element5 = await iterator.next() 106 | XCTAssertEqual(Tuple3(element5!), Tuple3((5, "b", "y"))) 107 | 108 | base.finish() 109 | 110 | let element6 = await iterator.next() 111 | XCTAssertNil(element6) 112 | 113 | let pastEnd = await iterator.next() 114 | XCTAssertNil(pastEnd) 115 | } 116 | 117 | func test_withLatestFrom_throws_when_base_throws_and_pastEnd_is_nil() async throws { 118 | let base = [1, 2, 3] 119 | let other1 = Indefinite(value: "a") 120 | let other2 = Indefinite(value: "x") 121 | 122 | let sequence = base.async.map { try throwOn(1, $0) }.withLatest(from: other1.async, other2.async) 123 | var iterator = sequence.makeAsyncIterator() 124 | 125 | do { 126 | let value = try await iterator.next() 127 | XCTFail("got \(value as Any) but expected throw") 128 | } catch { 129 | XCTAssertEqual(error as? MockError, MockError(code: 1701)) 130 | } 131 | 132 | let pastEnd = try await iterator.next() 133 | XCTAssertNil(pastEnd) 134 | } 135 | 136 | func test_withLatestFrom_throws_when_other1_throws_and_pastEnd_is_nil() async throws { 137 | let base = Indefinite(value: 1) 138 | let other1 = AsyncThrowingBufferedChannel() 139 | let other2 = Indefinite(value: "x").async 140 | 141 | let sequence = base.async.withLatest(from: other1, other2) 142 | var iterator = sequence.makeAsyncIterator() 143 | 144 | other1.fail(MockError(code: 1701)) 145 | 146 | do { 147 | var element: (Int, String, String)? 148 | repeat { 149 | element = try await iterator.next() 150 | } while element == nil 151 | XCTFail("got \(element as Any) but expected throw") 152 | } catch { 153 | XCTAssertEqual(error as? MockError, MockError(code: 1701)) 154 | } 155 | 156 | let pastEnd = try await iterator.next() 157 | XCTAssertNil(pastEnd) 158 | } 159 | 160 | func test_withLatestFrom_throws_when_other2_throws_and_pastEnd_is_nil() async throws { 161 | let base = Indefinite(value: 1) 162 | let other1 = Indefinite(value: "x").async 163 | let other2 = AsyncThrowingBufferedChannel() 164 | 165 | let sequence = base.async.withLatest(from: other1, other2) 166 | var iterator = sequence.makeAsyncIterator() 167 | 168 | other2.fail(MockError(code: 1701)) 169 | 170 | do { 171 | var element: (Int, String, String)? 172 | repeat { 173 | element = try await iterator.next() 174 | } while element == nil 175 | XCTFail("got \(element as Any) but expected throw") 176 | } catch { 177 | XCTAssertEqual(error as? MockError, MockError(code: 1701)) 178 | } 179 | 180 | let pastEnd = try await iterator.next() 181 | XCTAssertNil(pastEnd) 182 | } 183 | 184 | func test_withLatestFrom_finishes_loop_when_task_is_cancelled() async { 185 | let iterated = expectation(description: "The iteration has produced 1 element") 186 | let finished = expectation(description: "The iteration has finished") 187 | 188 | let base = Indefinite(value: 1).async 189 | let other1 = Indefinite(value: "a").async 190 | let other2 = Indefinite(value: "x").async 191 | 192 | let sequence = base.withLatest(from: other1, other2) 193 | 194 | let task = Task(priority: .high) { 195 | var iterator = sequence.makeAsyncIterator() 196 | 197 | var firstIteration = false 198 | var firstElement: (Int, String, String)? 199 | while let element = await iterator.next() { 200 | if !firstIteration { 201 | firstElement = element 202 | firstIteration = true 203 | iterated.fulfill() 204 | } 205 | } 206 | XCTAssertEqual(Tuple3(firstElement!), Tuple3((1, "a", "x"))) 207 | 208 | finished.fulfill() 209 | } 210 | 211 | // ensure the other task actually starts 212 | wait(for: [iterated], timeout: 5.0) 213 | 214 | // cancellation should ensure the loop finishes 215 | // without regards to the remaining underlying sequence 216 | task.cancel() 217 | wait(for: [finished], timeout: 5.0) 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Tests/Combiners/WithLatestFrom/AsyncWithLatestFromSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncWithLatestFromSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 01/04/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncWithLatestFromSequenceTests: XCTestCase { 12 | func test_withLatestFrom_uses_latest_element_from_other() async { 13 | // Timeline 14 | // base: -0---1 -2 -----3 ---4-----x--| 15 | // other: ---a-- -- -b-c-- -x----------| 16 | // expected: -----(1,a)-(2,a)-----(3,c)---(4,c)-x--| 17 | let baseHasProduced0 = expectation(description: "Base has produced 0") 18 | 19 | let otherHasProducedA = expectation(description: "Other has produced 'a'") 20 | let otherHasProducedC = expectation(description: "Other has produced 'c'") 21 | 22 | let base = AsyncBufferedChannel() 23 | let other = AsyncBufferedChannel() 24 | 25 | var sequence = base.withLatest(from: other) 26 | 27 | // expectations that ensure that "other" has really delivered 28 | // its elements before requesting the next element from "base" 29 | sequence.onOtherElement = { @Sendable element in 30 | if element == "a" { 31 | otherHasProducedA.fulfill() 32 | } 33 | 34 | if element == "c" { 35 | otherHasProducedC.fulfill() 36 | } 37 | } 38 | 39 | sequence.onBaseElement = { @Sendable element in 40 | if element == 0 { 41 | baseHasProduced0.fulfill() 42 | } 43 | } 44 | 45 | var iterator = sequence.makeAsyncIterator() 46 | 47 | Task { 48 | base.send(0) 49 | wait(for: [baseHasProduced0], timeout: 1.0) 50 | other.send("a") 51 | wait(for: [otherHasProducedA], timeout: 1.0) 52 | base.send(1) 53 | } 54 | 55 | let element1 = await iterator.next() 56 | XCTAssertEqual(Tuple2(element1!), Tuple2((1, "a"))) 57 | 58 | base.send(2) 59 | 60 | let element2 = await iterator.next() 61 | XCTAssertEqual(Tuple2(element2!), Tuple2((2, "a"))) 62 | 63 | Task { 64 | other.send("b") 65 | other.send("c") 66 | wait(for: [otherHasProducedC], timeout: 1.0) 67 | base.send(3) 68 | } 69 | 70 | let element3 = await iterator.next() 71 | XCTAssertEqual(Tuple2(element3!), Tuple2((3, "c"))) 72 | 73 | other.finish() 74 | base.send(4) 75 | 76 | let element4 = await iterator.next() 77 | XCTAssertEqual(Tuple2(element4!), Tuple2((4, "c"))) 78 | 79 | base.finish() 80 | 81 | let element5 = await iterator.next() 82 | XCTAssertNil(element5) 83 | 84 | let pastEnd = await iterator.next() 85 | XCTAssertNil(pastEnd) 86 | } 87 | 88 | func test_withLatestFrom_throws_when_base_throws_and_pastEnd_is_nil() async throws { 89 | let base = [1, 2, 3] 90 | let other = Indefinite(value: "a") 91 | 92 | let sequence = base.async.map { try throwOn(1, $0) }.withLatest(from: other.async) 93 | var iterator = sequence.makeAsyncIterator() 94 | 95 | do { 96 | let value = try await iterator.next() 97 | XCTFail("got \(value as Any) but expected throw") 98 | } catch { 99 | XCTAssertEqual(error as? MockError, MockError(code: 1701)) 100 | } 101 | 102 | let pastEnd = try await iterator.next() 103 | XCTAssertNil(pastEnd) 104 | } 105 | 106 | func test_withLatestFrom_throws_when_other_throws_and_pastEnd_is_nil() async throws { 107 | let base = Indefinite(value: 1) 108 | let other = AsyncThrowingBufferedChannel() 109 | let sequence = base.async.withLatest(from: other) 110 | var iterator = sequence.makeAsyncIterator() 111 | 112 | other.fail(MockError(code: 1701)) 113 | 114 | do { 115 | var element: (Int, String)? 116 | repeat { 117 | element = try await iterator.next() 118 | } while element == nil 119 | XCTFail("got \(element as Any) but expected throw") 120 | } catch { 121 | XCTAssertEqual(error as? MockError, MockError(code: 1701)) 122 | } 123 | 124 | let pastEnd = try await iterator.next() 125 | XCTAssertNil(pastEnd) 126 | } 127 | 128 | func test_withLatestFrom_finishes_loop_when_task_is_cancelled() async { 129 | let iterated = expectation(description: "The iteration has produced 1 element") 130 | let finished = expectation(description: "The iteration has finished") 131 | 132 | let base = Indefinite(value: 1).async 133 | let other = Indefinite(value: "a").async 134 | 135 | let sequence = base.withLatest(from: other) 136 | 137 | let task = Task(priority: .high) { 138 | var iterator = sequence.makeAsyncIterator() 139 | 140 | var firstIteration = false 141 | var firstElement: (Int, String)? 142 | while let element = await iterator.next() { 143 | if !firstIteration { 144 | firstElement = element 145 | firstIteration = true 146 | iterated.fulfill() 147 | } 148 | } 149 | XCTAssertEqual(Tuple2(firstElement!), Tuple2((1, "a"))) 150 | finished.fulfill() 151 | } 152 | 153 | // ensure the other task actually starts 154 | wait(for: [iterated], timeout: 1.0) 155 | 156 | // cancellation should ensure the loop finishes 157 | // without regards to the remaining underlying sequence 158 | task.cancel() 159 | wait(for: [finished], timeout: 1.0) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Tests/Creators/AsyncEmptySequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncEmptySequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 01/01/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncEmptySequenceTests: XCTestCase { 12 | func test_AsyncEmptySequence_immediately_resumes() async throws { 13 | var hasElement = false 14 | 15 | let sut = AsyncEmptySequence() 16 | for try await _ in sut { 17 | hasElement = true 18 | } 19 | 20 | XCTAssertFalse(hasElement) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Tests/Creators/AsyncFailSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncFailSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 04/01/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncFailSequenceTests: XCTestCase { 12 | func test_init_sets_error() { 13 | let mockError = MockError(code: Int.random(in: 0...100)) 14 | let sut = AsyncFailSequence(mockError) 15 | XCTAssertEqual(sut.error as? MockError, mockError) 16 | } 17 | 18 | func test_AsyncFailSequence_throws_expected_error() async { 19 | let mockError = MockError(code: Int.random(in: 0...100)) 20 | var receivedResult = [Int]() 21 | 22 | let sut = AsyncFailSequence(mockError) 23 | 24 | do { 25 | for try await result in sut { 26 | receivedResult.append(result) 27 | } 28 | } catch { 29 | XCTAssertEqual(error as? MockError, mockError) 30 | } 31 | 32 | XCTAssertTrue(receivedResult.isEmpty) 33 | } 34 | 35 | func test_AsyncFailSequence_returns_an_asyncSequence_that_finishes_without_error_when_task_is_cancelled() { 36 | let taskHasBeenCancelledExpectation = expectation(description: "The task has been cancelled") 37 | let sequenceHasFinishedExpectation = expectation(description: "The async sequence has finished") 38 | 39 | let failSequence = AsyncFailSequence(MockError(code: 1)) 40 | 41 | let task = Task { 42 | do { 43 | var iterator = failSequence.makeAsyncIterator() 44 | wait(for: [taskHasBeenCancelledExpectation], timeout: 1) 45 | while let _ = try await iterator.next() { 46 | XCTFail("The AsyncSequence should not output elements") 47 | } 48 | sequenceHasFinishedExpectation.fulfill() 49 | } catch { 50 | XCTFail("The AsyncSequence should not throw an error") 51 | } 52 | 53 | } 54 | 55 | task.cancel() 56 | 57 | taskHasBeenCancelledExpectation.fulfill() 58 | 59 | wait(for: [sequenceHasFinishedExpectation], timeout: 1) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Tests/Creators/AsyncJustSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncJustSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 04/01/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncJustSequenceTests: XCTestCase { 12 | func test_AsyncJustSequence_outputs_expected_element_and_finishes() async { 13 | var receivedResult = [Int]() 14 | 15 | let element = Int.random(in: 0...100) 16 | let sut = AsyncJustSequence(element) 17 | 18 | for await result in sut { 19 | receivedResult.append(result) 20 | } 21 | 22 | XCTAssertEqual(receivedResult, [element]) 23 | } 24 | 25 | func test_AsyncJustSequence_returns_an_asyncSequence_that_finishes_without_elements_when_task_is_cancelled() { 26 | let hasCancelledExpectation = expectation(description: "The task has been cancelled") 27 | let hasFinishedExpectation = expectation(description: "The AsyncSequence has finished") 28 | 29 | let justSequence = AsyncJustSequence(1) 30 | 31 | let task = Task { 32 | wait(for: [hasCancelledExpectation], timeout: 1) 33 | for await _ in justSequence { 34 | XCTFail("The AsyncSequence should not output elements") 35 | } 36 | hasFinishedExpectation.fulfill() 37 | } 38 | 39 | task.cancel() 40 | 41 | hasCancelledExpectation.fulfill() 42 | 43 | wait(for: [hasFinishedExpectation], timeout: 1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Tests/Creators/AsyncLazySequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncLazySequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 02/01/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncLazySequenceTests: XCTestCase { 12 | func test_AsyncLazySequence_returns_original_sequence() async { 13 | var receivedResult = [Int]() 14 | 15 | let sequence = [1, 2, 3, 4, 5] 16 | 17 | let sut = AsyncLazySequence(sequence) 18 | 19 | for await element in sut { 20 | receivedResult.append(element) 21 | } 22 | 23 | XCTAssertEqual(receivedResult, sequence) 24 | } 25 | 26 | func test_AsyncLazySequence_returns_an_asyncSequence_that_finishes_when_task_is_cancelled() { 27 | let canCancelExpectation = expectation(description: "The first element has been emitted") 28 | let hasCancelExceptation = expectation(description: "The task has been cancelled") 29 | 30 | let sequence = (0...1_000_000) 31 | 32 | let sut = AsyncLazySequence(sequence) 33 | 34 | let task = Task { 35 | var firstElement: Int? 36 | for await element in sut { 37 | firstElement = element 38 | canCancelExpectation.fulfill() 39 | wait(for: [hasCancelExceptation], timeout: 5) 40 | } 41 | XCTAssertEqual(firstElement!, 0) // the AsyncSequence is cancelled having only emitted the first element 42 | } 43 | 44 | wait(for: [canCancelExpectation], timeout: 5) // one element has been emitted, we can cancel the task 45 | 46 | task.cancel() 47 | 48 | hasCancelExceptation.fulfill() // we can release the lock in the for loop 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Tests/Creators/AsyncStream+PipeTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncStream+PipeTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 25/09/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncStream_PipeTests: XCTestCase { 12 | func test_pipe_produces_stream_input_and_output() { 13 | let finished = expectation(description: "The stream has finished") 14 | 15 | // Given 16 | let (input, output) = AsyncStream.pipe() 17 | 18 | Task { 19 | // Then 20 | var receivedElements = [Int]() 21 | for await element in output { 22 | receivedElements.append(element) 23 | } 24 | XCTAssertEqual(receivedElements, [1, 2]) 25 | finished.fulfill() 26 | } 27 | 28 | // When 29 | input.yield(1) 30 | input.yield(2) 31 | input.finish() 32 | 33 | wait(for: [finished], timeout: 1.0) 34 | } 35 | 36 | func test_pipe_produces_stream_input_and_output_that_can_throw() { 37 | let finished = expectation(description: "The stream has finished") 38 | 39 | // Given 40 | let (input, output) = AsyncThrowingStream.pipe() 41 | 42 | Task { 43 | // Then 44 | var receivedElements = [Int]() 45 | do { 46 | for try await element in output { 47 | receivedElements.append(element) 48 | } 49 | } catch { 50 | XCTAssertEqual(receivedElements, [1, 2]) 51 | XCTAssertEqual(error as? MockError, MockError(code: 1701)) 52 | } 53 | finished.fulfill() 54 | } 55 | 56 | // When 57 | input.yield(1) 58 | input.yield(2) 59 | input.yield(with: .failure(MockError(code: 1701))) 60 | 61 | wait(for: [finished], timeout: 1.0) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Tests/Creators/AsyncThrowingJustSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncThrowingJustSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 04/01/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncThrowingJustSequenceTests: XCTestCase { 12 | func test_AsyncThrowingJustSequence_outputs_expected_element_and_finishes() async throws { 13 | var receivedResult = [Int]() 14 | 15 | let element = Int.random(in: 0...100) 16 | let sut = AsyncThrowingJustSequence(element) 17 | 18 | for try await result in sut { 19 | receivedResult.append(result) 20 | } 21 | 22 | XCTAssertEqual(receivedResult, [element]) 23 | } 24 | 25 | func test_AsyncThrowingJustSequence_outputs_expected_element_from_closure_and_finishes() async throws { 26 | var receivedResult = [Int]() 27 | 28 | let element = Int.random(in: 0...100) 29 | let sut = AsyncThrowingJustSequence(factory: { 30 | return element 31 | }) 32 | 33 | for try await result in sut { 34 | receivedResult.append(result) 35 | } 36 | 37 | XCTAssertEqual(receivedResult, [element]) 38 | } 39 | 40 | func test_AsyncThrowingJustSequence_fails_and_finishes() async { 41 | let sut = AsyncThrowingJustSequence { throw MockError(code: 1701) } 42 | 43 | do { 44 | for try await _ in sut {} 45 | } catch { 46 | XCTAssertEqual(error as? MockError, MockError(code: 1701)) 47 | } 48 | } 49 | 50 | func test_AsyncThrowingJustSequence_returns_an_asyncSequence_that_finishes_without_elements_when_task_is_cancelled() { 51 | let hasCancelledExpectation = expectation(description: "The task has been cancelled") 52 | let hasFinishedExpectation = expectation(description: "The AsyncSequence has finished") 53 | 54 | let justSequence = AsyncThrowingJustSequence(1) 55 | 56 | let task = Task { 57 | wait(for: [hasCancelledExpectation], timeout: 1) 58 | for try await _ in justSequence { 59 | XCTFail("The AsyncSequence should not output elements") 60 | } 61 | hasFinishedExpectation.fulfill() 62 | } 63 | 64 | task.cancel() 65 | 66 | hasCancelledExpectation.fulfill() 67 | 68 | wait(for: [hasFinishedExpectation], timeout: 1) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Tests/Creators/AsyncTimerSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncTimerSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 06/03/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncTimerSequenceTests: XCTestCase { 12 | func testTimer_finishes_when_task_is_cancelled() { 13 | let canCancelExpectation = expectation(description: "the timer can be cancelled") 14 | let asyncSequenceHasFinishedExpectation = expectation(description: "The async sequence has finished") 15 | 16 | let sut = AsyncTimerSequence(priority: .userInitiated, every: .milliseconds(100)) 17 | 18 | let task = Task { 19 | var index = 1 20 | for try await _ in sut { 21 | if index == 10 { 22 | canCancelExpectation.fulfill() 23 | } 24 | index += 1 25 | } 26 | asyncSequenceHasFinishedExpectation.fulfill() 27 | } 28 | 29 | wait(for: [canCancelExpectation], timeout: 5) 30 | 31 | task.cancel() 32 | 33 | wait(for: [asyncSequenceHasFinishedExpectation], timeout: 5) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncHandleEventsSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncHandleEventsSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 01/01/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncHandleEventsSequenceTests: XCTestCase { 12 | func test_iteration_calls_blocks_when_not_cancelled_and_no_error() async throws { 13 | let received = ManagedCriticalState([String]()) 14 | 15 | let sourceSequence = [1, 2, 3, 4, 5].async 16 | let sut = sourceSequence.handleEvents { 17 | received.withCriticalRegion { $0.append("start") } 18 | } onElement: { element in 19 | received.withCriticalRegion { $0.append("\(element)") } 20 | } onCancel: { 21 | received.withCriticalRegion { $0.append("cancelled") } 22 | } onFinish: { completion in 23 | received.withCriticalRegion { $0.append("finish \(completion)") } 24 | } 25 | 26 | for try await _ in sut {} 27 | 28 | XCTAssertEqual(received.criticalState, ["start", "1", "2", "3", "4", "5", "finish finished"]) 29 | } 30 | 31 | func test_iteration_calls_onCancel_when_task_is_cancelled() { 32 | let firstElementHasBeenReceivedExpectation = expectation(description: "First element has been emitted") 33 | let taskHasBeenCancelledExpectation = expectation(description: "The task has been cancelled") 34 | let onCancelHasBeenCalledExpectation = expectation(description: "OnCancel has been called") 35 | 36 | let received = ManagedCriticalState([String]()) 37 | 38 | let sourceSequence = AsyncCurrentValueSubject(1) 39 | let sut = sourceSequence.handleEvents { 40 | received.withCriticalRegion { $0.append("start") } 41 | } onElement: { element in 42 | received.withCriticalRegion { $0.append("\(element)") } 43 | } onCancel: { 44 | received.withCriticalRegion { $0.append("cancelled") } 45 | onCancelHasBeenCalledExpectation.fulfill() 46 | } onFinish: { completion in 47 | received.withCriticalRegion { $0.append("finish \(completion)") } 48 | } 49 | 50 | let task = Task { 51 | for try await element in sut { 52 | if element == 1 { 53 | firstElementHasBeenReceivedExpectation.fulfill() 54 | } 55 | 56 | wait(for: [taskHasBeenCancelledExpectation], timeout: 1) 57 | } 58 | } 59 | 60 | wait(for: [firstElementHasBeenReceivedExpectation], timeout: 1) 61 | 62 | task.cancel() 63 | 64 | taskHasBeenCancelledExpectation.fulfill() 65 | 66 | wait(for: [onCancelHasBeenCalledExpectation], timeout: 1) 67 | 68 | XCTAssertEqual(received.criticalState, ["start", "1", "cancelled"]) 69 | } 70 | 71 | func test_iteration_calls_onFinish_with_failure_when_sequence_fails() async throws { 72 | let onFinishHasBeenCalledExpectation = expectation(description: "OnFinish has been called") 73 | 74 | let received = ManagedCriticalState([String]()) 75 | 76 | let expectedError = MockError(code: Int.random(in: 0...100)) 77 | 78 | let sourceSequence = AsyncFailSequence(expectedError) 79 | let sut = sourceSequence.handleEvents { 80 | received.withCriticalRegion { $0.append("start") } 81 | } onElement: { element in 82 | received.withCriticalRegion { $0.append("\(element)") } 83 | } onCancel: { 84 | received.withCriticalRegion { $0.append("cancelled") } 85 | } onFinish: { completion in 86 | if case let .failure(error) = completion { 87 | XCTAssertEqual(error as? MockError, expectedError) 88 | } 89 | onFinishHasBeenCalledExpectation.fulfill() 90 | } 91 | 92 | do { 93 | for try await _ in sut {} 94 | } catch { 95 | XCTAssertEqual(error as? MockError, expectedError) 96 | } 97 | 98 | await waitForExpectations(timeout: 1) 99 | } 100 | 101 | func test_iteration_finishes_when_task_is_cancelled() { 102 | let canCancelExpectation = expectation(description: "The first element has been emitted") 103 | let hasCancelExceptation = expectation(description: "The task has been cancelled") 104 | let taskHasFinishedExpectation = expectation(description: "The task has finished") 105 | 106 | let sut = (0...1_000_000) 107 | 108 | let handledSequence = sut.async.handleEvents() 109 | 110 | let task = Task { 111 | var firstElement: Int? 112 | for try await element in handledSequence { 113 | firstElement = element 114 | canCancelExpectation.fulfill() 115 | wait(for: [hasCancelExceptation], timeout: 5) 116 | } 117 | XCTAssertEqual(firstElement, 0) 118 | taskHasFinishedExpectation.fulfill() 119 | } 120 | 121 | wait(for: [canCancelExpectation], timeout: 5) // one element has been emitted, we can cancel the task 122 | 123 | task.cancel() 124 | 125 | hasCancelExceptation.fulfill() // we can release the lock in the for loop 126 | 127 | wait(for: [taskHasFinishedExpectation], timeout: 5) // task has been cancelled and has finished 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncMapToResultSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncMapToResultSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 28/08/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncMapToResultSequenceTests: XCTestCase { 12 | func test_mapToResult_maps_to_success_when_base_doesnt_throw() async throws { 13 | // Given 14 | let sut = AsyncJustSequence(1).mapToResult() 15 | var received: Result? 16 | 17 | // When 18 | for await element in sut { 19 | received = element 20 | } 21 | 22 | // Then 23 | let receivedValue = try received?.get() 24 | XCTAssertEqual(receivedValue, 1) 25 | } 26 | 27 | func test_mapToResult_maps_to_failure_when_base_throws() async { 28 | // Given 29 | let sut = AsyncFailSequence(MockError(code: 1701)).mapToResult() 30 | var received: Result? 31 | 32 | // When 33 | for await element in sut { 34 | received = element 35 | } 36 | 37 | // Then 38 | XCTAssertThrowsError(try received?.get()) { error in 39 | XCTAssertEqual(error as? MockError, MockError(code: 1701)) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncMulticastSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncMulticastSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 21/02/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | private class SpyAsyncSequenceForNumberOfIterators: AsyncSequence { 12 | typealias Element = Element 13 | typealias AsyncIterator = Iterator 14 | 15 | let element: Element 16 | let numberOfTimes: Int 17 | 18 | var numberOfIterators = 0 19 | 20 | init(element: Element, numberOfTimes: Int) { 21 | self.element = element 22 | self.numberOfTimes = numberOfTimes 23 | } 24 | 25 | func makeAsyncIterator() -> AsyncIterator { 26 | self.numberOfIterators += 1 27 | return Iterator(element: self.element, numberOfTimes: self.numberOfTimes) 28 | } 29 | 30 | struct Iterator: AsyncIteratorProtocol { 31 | let element: Element 32 | var numberOfTimes: Int 33 | 34 | mutating func next() async throws -> Element? { 35 | guard self.numberOfTimes > 0 else { return nil } 36 | self.numberOfTimes -= 1 37 | return element 38 | } 39 | } 40 | } 41 | 42 | final class AsyncMulticastSequenceTests: XCTestCase { 43 | func test_multiple_loops_receive_elements_from_single_baseIterator() { 44 | let taskHaveIterators = expectation(description: "All tasks have their iterator") 45 | taskHaveIterators.expectedFulfillmentCount = 2 46 | 47 | let tasksHaveFinishedExpectation = expectation(description: "Tasks have finished") 48 | tasksHaveFinishedExpectation.expectedFulfillmentCount = 2 49 | 50 | let spyUpstreamSequence = SpyAsyncSequenceForNumberOfIterators(element: 1, numberOfTimes: 3) 51 | let stream = AsyncThrowingPassthroughSubject() 52 | let sut = spyUpstreamSequence.multicast(stream) 53 | 54 | Task { 55 | var receivedElement = [Int]() 56 | var iterator = sut.makeAsyncIterator() 57 | taskHaveIterators.fulfill() 58 | while let element = try await iterator.next() { 59 | receivedElement.append(element) 60 | } 61 | XCTAssertEqual(receivedElement, [1, 1, 1]) 62 | tasksHaveFinishedExpectation.fulfill() 63 | } 64 | 65 | Task { 66 | var receivedElement = [Int]() 67 | var iterator = sut.makeAsyncIterator() 68 | taskHaveIterators.fulfill() 69 | while let element = try await iterator.next() { 70 | receivedElement.append(element) 71 | } 72 | XCTAssertEqual(receivedElement, [1, 1, 1]) 73 | tasksHaveFinishedExpectation.fulfill() 74 | } 75 | 76 | wait(for: [taskHaveIterators], timeout: 1) 77 | 78 | sut.connect() 79 | 80 | wait(for: [tasksHaveFinishedExpectation], timeout: 1) 81 | 82 | XCTAssertEqual(spyUpstreamSequence.numberOfIterators, 1) 83 | } 84 | 85 | func test_multiple_loops_uses_provided_stream() { 86 | let taskHaveIterators = expectation(description: "All tasks have their iterator") 87 | taskHaveIterators.expectedFulfillmentCount = 3 88 | 89 | let tasksHaveFinishedExpectation = expectation(description: "Tasks have finished") 90 | tasksHaveFinishedExpectation.expectedFulfillmentCount = 3 91 | 92 | let stream = AsyncThrowingPassthroughSubject() 93 | let spyUpstreamSequence = SpyAsyncSequenceForNumberOfIterators(element: 1, numberOfTimes: 3) 94 | let sut = spyUpstreamSequence.multicast(stream) 95 | 96 | Task { 97 | var receivedElement = [Int]() 98 | var iterator = sut.makeAsyncIterator() 99 | taskHaveIterators.fulfill() 100 | while let element = try await iterator.next() { 101 | receivedElement.append(element) 102 | } 103 | XCTAssertEqual(receivedElement, [1, 1, 1]) 104 | tasksHaveFinishedExpectation.fulfill() 105 | } 106 | 107 | Task { 108 | var receivedElement = [Int]() 109 | var iterator = sut.makeAsyncIterator() 110 | taskHaveIterators.fulfill() 111 | while let element = try await iterator.next() { 112 | receivedElement.append(element) 113 | } 114 | XCTAssertEqual(receivedElement, [1, 1, 1]) 115 | tasksHaveFinishedExpectation.fulfill() 116 | } 117 | 118 | Task { 119 | var receivedElement = [Int]() 120 | var iterator = sut.makeAsyncIterator() 121 | taskHaveIterators.fulfill() 122 | while let element = try await iterator.next() { 123 | receivedElement.append(element) 124 | } 125 | XCTAssertEqual(receivedElement, [1, 1, 1]) 126 | tasksHaveFinishedExpectation.fulfill() 127 | } 128 | 129 | wait(for: [taskHaveIterators], timeout: 1) 130 | 131 | sut.connect() 132 | 133 | wait(for: [tasksHaveFinishedExpectation], timeout: 1) 134 | 135 | XCTAssertEqual(spyUpstreamSequence.numberOfIterators, 1) 136 | } 137 | 138 | func test_multicast_propagates_error_when_autoconnect() async { 139 | let expectedError = MockError(code: Int.random(in: 0...100)) 140 | 141 | let stream = AsyncThrowingPassthroughSubject() 142 | 143 | let sut = AsyncFailSequence(expectedError) 144 | .prepend(1) 145 | .multicast(stream) 146 | .autoconnect() 147 | 148 | var receivedElement = [Int]() 149 | do { 150 | for try await element in sut { 151 | receivedElement.append(element) 152 | } 153 | XCTFail("The iteration should fail") 154 | } catch { 155 | XCTAssertEqual(receivedElement, [1]) 156 | XCTAssertEqual(error as? MockError, expectedError) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncPrependSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncPrependSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 01/01/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncPrependSequenceTests: XCTestCase { 12 | func testPrepend_prepends_an_element_to_the_source_asyncSequence() async throws { 13 | let expectedResult = [0, 1, 2, 3, 4, 5] 14 | var receivedResult = [Int]() 15 | 16 | let sut = [1, 2, 3, 4, 5].async 17 | 18 | let prependedSequence = sut.prepend(0) 19 | for try await element in prependedSequence { 20 | receivedResult.append(element) 21 | } 22 | 23 | XCTAssertEqual(receivedResult, expectedResult) 24 | } 25 | 26 | func testPrepend_finishes_when_task_is_cancelled() { 27 | let canCancelExpectation = expectation(description: "The first element has been emitted") 28 | let hasCancelExceptation = expectation(description: "The task has been cancelled") 29 | let taskHasFinishedExpectation = expectation(description: "The task has finished") 30 | 31 | let sut = [1, 2, 3, 4, 5].async 32 | 33 | let prependedSequence = sut.prepend(0) 34 | 35 | let task = Task { 36 | var firstElement: Int? 37 | for try await element in prependedSequence { 38 | firstElement = element 39 | canCancelExpectation.fulfill() 40 | wait(for: [hasCancelExceptation], timeout: 5) 41 | } 42 | XCTAssertEqual(firstElement, 0) 43 | taskHasFinishedExpectation.fulfill() 44 | } 45 | 46 | wait(for: [canCancelExpectation], timeout: 5) // one element has been emitted, we can cancel the task 47 | 48 | task.cancel() 49 | 50 | hasCancelExceptation.fulfill() // we can release the lock in the for loop 51 | 52 | wait(for: [taskHasFinishedExpectation], timeout: 5) // task has been cancelled and has finished 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncScanSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncScanSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 31/12/2021. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncScanSequenceTests: XCTestCase { 12 | func testScan_applies_the_reducer_and_return_partial_results() async throws { 13 | let expectedResult = ["1", "12", "123", "1234", "12345"] 14 | var receivedResult = [String]() 15 | 16 | let sourceSequence = [1, 2, 3, 4, 5].async 17 | let reducer: @Sendable (String, Int) async -> String = { accumulator, nextValue in 18 | accumulator + "\(nextValue)" 19 | } 20 | 21 | let sut = sourceSequence.scan("", reducer) 22 | for try await element in sut { 23 | receivedResult.append(element) 24 | } 25 | 26 | XCTAssertEqual(receivedResult, expectedResult) 27 | } 28 | 29 | func testScan_finishes_when_task_is_cancelled() { 30 | let canCancelExpectation = expectation(description: "The first element has been emitted") 31 | let hasCancelExceptation = expectation(description: "The task has been cancelled") 32 | let taskHasFinishedExpectation = expectation(description: "The task has finished") 33 | 34 | let sourceSequence = [1, 2, 3, 4, 5].async 35 | let reducer: @Sendable (String, Int) async -> String = { accumulator, nextValue in 36 | accumulator + "\(nextValue)" 37 | } 38 | 39 | let sut = sourceSequence.scan("", reducer) 40 | 41 | let task = Task { 42 | var firstElement: String? 43 | for try await element in sut { 44 | firstElement = element 45 | canCancelExpectation.fulfill() 46 | wait(for: [hasCancelExceptation], timeout: 5) 47 | } 48 | XCTAssertEqual(firstElement, "1") 49 | taskHasFinishedExpectation.fulfill() 50 | } 51 | 52 | wait(for: [canCancelExpectation], timeout: 5) // one element has been emitted, we can cancel the task 53 | 54 | task.cancel() 55 | 56 | hasCancelExceptation.fulfill() // we can release the lock in the for loop 57 | 58 | wait(for: [taskHasFinishedExpectation], timeout: 5) // task has been cancelled and has finished 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncSequence+AssignTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+AssignTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 02/02/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | private class Root { 12 | var successiveValues = [String]() 13 | 14 | var property: String = "" { 15 | didSet { 16 | self.successiveValues.append(self.property) 17 | } 18 | } 19 | } 20 | 21 | final class AsyncSequence_AssignTests: XCTestCase { 22 | func testAssign_sets_elements_on_the_root() async throws { 23 | let root = Root() 24 | let sut = AsyncLazySequence(["1", "2", "3"]) 25 | try await sut.assign(to: \.property, on: root) 26 | XCTAssertEqual(root.successiveValues, ["1", "2", "3"]) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncSequence+CollectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+CollectTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 01/01/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | private class SpyAsyncSequence: AsyncSequence, AsyncIteratorProtocol { 12 | typealias Element = Base.Element 13 | typealias AsyncIterator = SpyAsyncSequence 14 | 15 | private var baseIterator: Base.Iterator 16 | var emittedElements = [Base.Element]() 17 | 18 | init(base: Base) { 19 | self.baseIterator = base.makeIterator() 20 | } 21 | 22 | func next() async throws -> Element? { 23 | let nextElement = self.baseIterator.next() 24 | if let element = nextElement { 25 | self.emittedElements.append(element) 26 | } 27 | return nextElement 28 | } 29 | 30 | func makeAsyncIterator() -> AsyncIterator { 31 | self 32 | } 33 | } 34 | 35 | final class AsyncSequence_CollectTests: XCTestCase { 36 | func test_collect_iterates_over_the_asyncSequence_and_returns_elements_in_array() async throws { 37 | let expectedResult = [1, 2, 3, 4, 5] 38 | 39 | let sut = SpyAsyncSequence(base: [1, 2, 3, 4, 5]) 40 | 41 | let elements = try await sut.collect() 42 | 43 | XCTAssertEqual(elements, expectedResult) 44 | } 45 | 46 | func test_collect_iterates_over_the_asyncSequence_and_gives_the_elements_to_the_closure() async throws { 47 | let expectedResult = [1, 2, 3, 4, 5] 48 | var receivedElements = [Int]() 49 | 50 | let sut = SpyAsyncSequence(base: [1, 2, 3, 4, 5]) 51 | 52 | try await sut.collect { element in 53 | receivedElements.append(element) 54 | } 55 | 56 | XCTAssertEqual(receivedElements, expectedResult) 57 | XCTAssertEqual(sut.emittedElements, expectedResult) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncSequence+EraseToAnyAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+EraseToAnyAsyncSequenceTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 04/01/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | final class AsyncSequence_EraseToAnyAsyncSequenceTests: XCTestCase { 12 | func test_AnyAsyncSequence_gives_sames_values_as_original_sequence() async throws { 13 | let expectedValues = (0...4).map { _ in Int.random(in: 0...100) } 14 | var receivedValues = [Int]() 15 | 16 | let baseSequence = expectedValues.async 17 | let sut = baseSequence.eraseToAnyAsyncSequence() 18 | 19 | for try await element in sut { 20 | receivedValues.append(element) 21 | } 22 | 23 | XCTAssertEqual(receivedValues, expectedValues) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncSequence+ShareTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSequence+ShareTests.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 03/03/2022. 6 | // 7 | 8 | import AsyncExtensions 9 | import XCTest 10 | 11 | private extension DispatchTimeInterval { 12 | var nanoseconds: UInt64 { 13 | switch self { 14 | case .nanoseconds(let value) where value >= 0: return UInt64(value) 15 | case .microseconds(let value) where value >= 0: return UInt64(value) * 1000 16 | case .milliseconds(let value) where value >= 0: return UInt64(value) * 1_000_000 17 | case .seconds(let value) where value >= 0: return UInt64(value) * 1_000_000_000 18 | case .never: return .zero 19 | default: return .zero 20 | } 21 | } 22 | } 23 | 24 | private struct LongAsyncSequence: AsyncSequence, AsyncIteratorProtocol { 25 | typealias Element = Element 26 | typealias AsyncIterator = LongAsyncSequence 27 | 28 | var elements: IndexingIterator<[Element]> 29 | let interval: DispatchTimeInterval 30 | var currentIndex = -1 31 | let failAt: Int? 32 | var hasEmitted = false 33 | let onCancel: () -> Void 34 | 35 | init(elements: [Element], interval: DispatchTimeInterval = .seconds(0), failAt: Int? = nil, onCancel: @escaping () -> Void = {}) { 36 | self.onCancel = onCancel 37 | self.elements = elements.makeIterator() 38 | self.failAt = failAt 39 | self.interval = interval 40 | } 41 | 42 | mutating func next() async throws -> Element? { 43 | return try await withTaskCancellationHandler { 44 | try await Task.sleep(nanoseconds: self.interval.nanoseconds) 45 | self.currentIndex += 1 46 | if self.currentIndex == self.failAt { 47 | throw MockError(code: 0) 48 | } 49 | return self.elements.next() 50 | } onCancel: {[onCancel] in 51 | onCancel() 52 | } 53 | } 54 | 55 | func makeAsyncIterator() -> AsyncIterator { 56 | self 57 | } 58 | } 59 | 60 | final class AsyncSequence_ShareTests: XCTestCase { 61 | func test_share_multicasts_values_to_clientLoops() { 62 | let tasksHaveFinishedExpectation = expectation(description: "the tasks have finished") 63 | tasksHaveFinishedExpectation.expectedFulfillmentCount = 2 64 | 65 | let sut = LongAsyncSequence( 66 | elements: ["1", "2", "3"], 67 | interval: .milliseconds(200) 68 | ).share() 69 | 70 | Task(priority: .high) { 71 | var received = [String]() 72 | try await sut.collect { received.append($0) } 73 | XCTAssertEqual(received, ["1", "2", "3"]) 74 | tasksHaveFinishedExpectation.fulfill() 75 | } 76 | 77 | Task(priority: .high) { 78 | var received = [String]() 79 | try await sut.collect { received.append($0) } 80 | XCTAssertEqual(received, ["1", "2", "3"]) 81 | tasksHaveFinishedExpectation.fulfill() 82 | } 83 | 84 | waitForExpectations(timeout: 5) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Tests/Operators/AsyncSwitchToLatestSequenceTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSwitchToLatestSequence.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 04/01/2022. 6 | // 7 | 8 | @testable import AsyncExtensions 9 | import XCTest 10 | 11 | private extension DispatchTimeInterval { 12 | var nanoseconds: UInt64 { 13 | switch self { 14 | case .nanoseconds(let value) where value >= 0: return UInt64(value) 15 | case .microseconds(let value) where value >= 0: return UInt64(value) * 1000 16 | case .milliseconds(let value) where value >= 0: return UInt64(value) * 1_000_000 17 | case .seconds(let value) where value >= 0: return UInt64(value) * 1_000_000_000 18 | case .never: return .zero 19 | default: return .zero 20 | } 21 | } 22 | } 23 | 24 | private struct LongAsyncSequence: AsyncSequence, AsyncIteratorProtocol { 25 | typealias Element = Element 26 | typealias AsyncIterator = LongAsyncSequence 27 | 28 | var elements: IndexingIterator<[Element]> 29 | let interval: DispatchTimeInterval 30 | var currentIndex = -1 31 | let failAt: Int? 32 | var hasEmitted = false 33 | let onCancel: () -> Void 34 | 35 | init(elements: [Element], interval: DispatchTimeInterval = .seconds(0), failAt: Int? = nil, onCancel: @escaping () -> Void = {}) { 36 | self.onCancel = onCancel 37 | self.elements = elements.makeIterator() 38 | self.failAt = failAt 39 | self.interval = interval 40 | } 41 | 42 | mutating func next() async throws -> Element? { 43 | return try await withTaskCancellationHandler { [onCancel] in 44 | onCancel() 45 | } operation: { 46 | try await Task.sleep(nanoseconds: self.interval.nanoseconds) 47 | self.currentIndex += 1 48 | if self.currentIndex == self.failAt { 49 | throw MockError(code: 0) 50 | } 51 | return self.elements.next() 52 | } 53 | } 54 | 55 | func makeAsyncIterator() -> AsyncIterator { 56 | self 57 | } 58 | } 59 | 60 | final class AsyncSwitchToLatestSequenceTests: XCTestCase { 61 | func testSwitchToLatest_switches_to_latest_asyncSequence_and_cancels_previous_ones() async throws { 62 | var asyncSequence1IsCancelled = false 63 | var asyncSequence2IsCancelled = false 64 | var asyncSequence3IsCancelled = false 65 | 66 | let childAsyncSequence1 = LongAsyncSequence( 67 | elements: [1, 2, 3], 68 | interval: .milliseconds(200), 69 | onCancel: { asyncSequence1IsCancelled = true } 70 | ) 71 | .prepend(0) 72 | let childAsyncSequence2 = LongAsyncSequence( 73 | elements: [5, 6, 7], 74 | interval: .milliseconds(200), 75 | onCancel: { asyncSequence2IsCancelled = true } 76 | ) 77 | .prepend(4) 78 | let childAsyncSequence3 = LongAsyncSequence( 79 | elements: [9, 10, 11], 80 | interval: .milliseconds(200), 81 | onCancel: { asyncSequence3IsCancelled = true } 82 | ) 83 | .prepend(8) 84 | 85 | let mainAsyncSequence = LongAsyncSequence(elements: [childAsyncSequence1, childAsyncSequence2, childAsyncSequence3], 86 | interval: .milliseconds(30), 87 | onCancel: {}) 88 | 89 | let sut = mainAsyncSequence.switchToLatest() 90 | 91 | var receivedElements = [Int]() 92 | let expectedElements = [0, 4, 8, 9, 10, 11] 93 | for try await element in sut { 94 | receivedElements.append(element) 95 | } 96 | 97 | XCTAssertEqual(receivedElements, expectedElements) 98 | XCTAssertTrue(asyncSequence1IsCancelled) 99 | XCTAssertTrue(asyncSequence2IsCancelled) 100 | XCTAssertFalse(asyncSequence3IsCancelled) 101 | } 102 | 103 | func testSwitchToLatest_propagates_errors_when_base_sequence_fails() async { 104 | let sequences = [ 105 | AsyncLazySequence([1, 2, 3]).eraseToAnyAsyncSequence(), 106 | AsyncLazySequence([4, 5, 6]).eraseToAnyAsyncSequence(), 107 | AsyncLazySequence([7, 8, 9]).eraseToAnyAsyncSequence(), // should fail here 108 | AsyncLazySequence([10, 11, 12]).eraseToAnyAsyncSequence(), 109 | ] 110 | 111 | let sourceSequence = LongAsyncSequence(elements: sequences, interval: .milliseconds(100), failAt: 2) 112 | 113 | let sut = sourceSequence.switchToLatest() 114 | 115 | var received = [Int]() 116 | 117 | do { 118 | for try await element in sut { 119 | received.append(element) 120 | } 121 | XCTFail("The sequence should fail") 122 | } catch { 123 | XCTAssertEqual(received, [1, 2, 3, 4, 5, 6]) 124 | XCTAssert(error is MockError) 125 | } 126 | } 127 | 128 | func testSwitchToLatest_propagates_errors_when_child_sequence_fails() async { 129 | let expectedError = MockError(code: Int.random(in: 0...100)) 130 | 131 | let sequences = [ 132 | AsyncJustSequence(1).eraseToAnyAsyncSequence(), 133 | AsyncFailSequence(expectedError).eraseToAnyAsyncSequence(), // should fail 134 | AsyncJustSequence(2).eraseToAnyAsyncSequence() 135 | ] 136 | 137 | let sourceSequence = LongAsyncSequence(elements: sequences, interval: .milliseconds(100)) 138 | 139 | let sut = sourceSequence.switchToLatest() 140 | 141 | var received = [Int]() 142 | 143 | do { 144 | for try await element in sut { 145 | received.append(element) 146 | } 147 | XCTFail("The sequence should fail") 148 | } catch { 149 | XCTAssertEqual(received, [1]) 150 | XCTAssertEqual(error as? MockError, expectedError) 151 | } 152 | } 153 | 154 | func testSwitchToLatest_finishes_when_task_is_cancelled_after_switched() { 155 | let canCancelExpectation = expectation(description: "The first element has been emitted") 156 | let hasCancelExceptation = expectation(description: "The task has been cancelled") 157 | let taskHasFinishedExpectation = expectation(description: "The task has finished") 158 | 159 | let sourceSequence = [1, 2, 3].async 160 | let mappedSequence = sourceSequence.map { element in LongAsyncSequence( 161 | elements: [element], 162 | interval: .milliseconds(50), 163 | onCancel: {} 164 | )} 165 | let sut = mappedSequence.switchToLatest() 166 | 167 | let task = Task { 168 | var firstElement: Int? 169 | for try await element in sut { 170 | firstElement = element 171 | canCancelExpectation.fulfill() 172 | wait(for: [hasCancelExceptation], timeout: 5) 173 | } 174 | XCTAssertEqual(firstElement, 3) 175 | taskHasFinishedExpectation.fulfill() 176 | } 177 | 178 | wait(for: [canCancelExpectation], timeout: 5) // one element has been emitted, we can cancel the task 179 | 180 | task.cancel() 181 | 182 | hasCancelExceptation.fulfill() // we can release the lock in the for loop 183 | 184 | wait(for: [taskHasFinishedExpectation], timeout: 5) // task has been cancelled and has finished 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Tests/Supporting/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Helpers.swift 3 | // 4 | // 5 | // Created by Thibault Wittemberg on 11/09/2022. 6 | // 7 | 8 | struct Indefinite: Sequence, IteratorProtocol, Sendable { 9 | let value: Element 10 | 11 | func next() -> Element? { 12 | return value 13 | } 14 | 15 | func makeIterator() -> Indefinite { 16 | self 17 | } 18 | } 19 | 20 | func throwOn(_ toThrowOn: T, _ value: T) throws -> T { 21 | if value == toThrowOn { 22 | throw MockError(code: 1701) 23 | } 24 | return value 25 | } 26 | 27 | struct MockError: Error, Equatable { 28 | let code: Int 29 | } 30 | 31 | struct Tuple2: Equatable { 32 | let value1: T1 33 | let value2: T2 34 | 35 | init(_ values: (T1, T2)) { 36 | self.value1 = values.0 37 | self.value2 = values.1 38 | } 39 | } 40 | 41 | struct Tuple3: Equatable { 42 | let value1: T1 43 | let value2: T2 44 | let value3: T3 45 | 46 | init(_ values: (T1, T2, T3)) { 47 | self.value1 = values.0 48 | self.value2 = values.1 49 | self.value3 = values.2 50 | } 51 | } 52 | --------------------------------------------------------------------------------