├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .spi.yml ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── LICENSE.md ├── Package.swift ├── README.md ├── Sources └── Asynchrone │ ├── Common │ ├── ErrorMechanism.swift │ └── RethrowingAccessor.swift │ ├── Documentation.docc │ └── Asynchrone.md │ ├── Extensions │ ├── AsyncSequence+Extension.swift │ ├── AsyncStream+Extension.swift │ ├── AsyncThrowingStream+Extension.swift │ ├── Task+Extension.swift │ └── TimeInterval+Extension.swift │ └── Sequences │ ├── AnyAsyncSequenceable.swift │ ├── AnyThrowingAsyncSequenceable.swift │ ├── AsyncSequenceCompletion.swift │ ├── CatchErrorAsyncSequence.swift │ ├── ChainAsyncSequenceable.swift │ ├── CombineLatest3AsyncSequence.swift │ ├── CombineLatestAsyncSequence.swift │ ├── CurrentElementAsyncSequence.swift │ ├── DebounceAsyncSequence.swift │ ├── DelayAsyncSequence.swift │ ├── Empty.swift │ ├── Fail.swift │ ├── Just.swift │ ├── Merge3AsyncSequence.swift │ ├── MergeAsyncSequence.swift │ ├── NotificationCenterAsyncSequence.swift │ ├── PassthroughAsyncSequence.swift │ ├── RemoveDuplicatesAsyncSequence.swift │ ├── ReplaceErrorAsyncSequence.swift │ ├── SequenceAsyncSequence.swift │ ├── SharedAsyncSequence.swift │ ├── ThrottleAsyncSequence.swift │ ├── ThrowingPassthroughAsyncSequence.swift │ ├── TimerAsyncSequence.swift │ ├── Zip3AsyncSequence.swift │ └── ZipAsyncSequence.swift └── Tests └── AsynchroneTests ├── Assertion.swift ├── Extensions ├── AsyncSequenceTests.swift └── TimeIntervalTests.swift ├── Sequences ├── AnyAsyncSequenceableTests.swift ├── AnyThrowingAsyncSequenceableTests.swift ├── CatchErrorAsyncSequenceTests.swift ├── ChainAsyncSequenceTests.swift ├── CombineLatest3AsyncSequenceTests.swift ├── CombineLatestAsyncSequenceTests.swift ├── CurrentElementAsyncSequenceTests.swift ├── DebounceAsyncSequenceTests.swift ├── DelayAsyncSequenceTests.swift ├── EmptyTests.swift ├── FailTests.swift ├── JustTests.swift ├── Merge3AsyncSequenceTests.swift ├── MergeAsyncSequenceTests.swift ├── NotificationCenterAsyncSequenceTests.swift ├── PassthroughAsyncSequenceTests.swift ├── RemoveDuplicatesAsyncSequenceTests.swift ├── ReplaceErrorAsyncSequenceTests.swift ├── SequenceAsyncSequenceTests.swift ├── SharedAsyncSequenceTests.swift ├── ThrottleAsyncSequenceTests.swift ├── ThrowingPassthroughAsyncSequenceTests.swift ├── TimerAsyncSequenceTests.swift └── ZipAsyncSequenceTests.swift └── TestError.swift /.gitattributes: -------------------------------------------------------------------------------- 1 | *.css linguist-detectable=false 2 | *.js linguist-detectable=false 3 | *.html linguist-detectable=false 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: reddavis 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | test: 13 | name: Unit Tests 14 | runs-on: macOS-12 15 | env: 16 | DEVELOPER_DIR: /Applications/Xcode_14.1.app/Contents/Developer 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Run Tests 20 | run: swift test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | /*.xcodeproj 5 | xcuserdata/ 6 | DerivedData/ 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Asynchrone] 5 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Red Davis 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.7 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: "Asynchrone", 8 | platforms: [ 9 | .iOS(.v14), 10 | .macOS(.v12), 11 | .watchOS(.v6), 12 | .tvOS(.v14) 13 | ], 14 | products: [ 15 | // Products define the executables and libraries a package produces, and make them visible to other packages. 16 | .library( 17 | name: "Asynchrone", 18 | targets: ["Asynchrone"]), 19 | ], 20 | dependencies: [ 21 | // Dependencies declare other packages that this package depends on. 22 | // .package(url: /* package url */, from: "1.0.0"), 23 | ], 24 | targets: [ 25 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 26 | // Targets can depend on other targets in this package, and on products in packages this package depends on. 27 | .target( 28 | name: "Asynchrone", 29 | dependencies: []), 30 | .testTarget( 31 | name: "AsynchroneTests", 32 | dependencies: ["Asynchrone"]), 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asynchrone 2 | 3 | Extensions and additions for Swift's async sequence. 4 | 5 | ## Requirements 6 | 7 | - iOS 14.0+ 8 | - macOS 12.0+ 9 | - watchOS 6.0+ 10 | - tvOS 14.0+ 11 | 12 | ## Installation 13 | 14 | ### Swift Package Manager 15 | 16 | In Xcode: 17 | 18 | 1. Click `Project`. 19 | 2. Click `Package Dependencies`. 20 | 3. Click `+`. 21 | 4. Enter package URL: `https://github.com/reddavis/Asynchrone`. 22 | 5. Add `Asynchrone` to your app target. 23 | 24 | ## Documentation 25 | 26 | Documentation can be found [here](https://swiftpackageindex.com/reddavis/Asynchrone/main/documentation/asynchrone). 27 | 28 | ## Overview 29 | 30 | ### AsyncSequence 31 | 32 | ### Extensions 33 | 34 | #### Assign 35 | 36 | ```swift 37 | class MyClass { 38 | var value: Int = 0 { 39 | didSet { print("Set to \(self.value)") } 40 | } 41 | } 42 | 43 | 44 | let sequence = AsyncStream { continuation in 45 | continuation.yield(1) 46 | continuation.yield(2) 47 | continuation.yield(3) 48 | continuation.finish() 49 | } 50 | 51 | let object = MyClass() 52 | sequence.assign(to: \.value, on: object) 53 | 54 | // Prints: 55 | // Set to 1 56 | // Set to 2 57 | // Set to 3 58 | ``` 59 | 60 | #### First 61 | 62 | ```swift 63 | let sequence = AsyncStream { continuation in 64 | continuation.yield(1) 65 | continuation.yield(2) 66 | continuation.yield(3) 67 | continuation.finish() 68 | } 69 | 70 | print(await sequence.first()) 71 | 72 | // Prints: 73 | // 1 74 | ``` 75 | 76 | #### Last 77 | 78 | ```swift 79 | let sequence = AsyncStream { continuation in 80 | continuation.yield(1) 81 | continuation.yield(2) 82 | continuation.yield(3) 83 | continuation.finish() 84 | } 85 | 86 | print(await sequence.last()) 87 | 88 | // Prints: 89 | // 3 90 | ``` 91 | 92 | #### Collect 93 | 94 | ```swift 95 | let sequence = AsyncStream { continuation in 96 | continuation.yield(1) 97 | continuation.yield(2) 98 | continuation.yield(3) 99 | continuation.finish() 100 | } 101 | 102 | print(await sequence.collect()) 103 | 104 | // Prints: 105 | // [1, 2, 3] 106 | ``` 107 | 108 | #### Sink 109 | 110 | ```swift 111 | let sequence = .init { continuation in 112 | continuation.yield(1) 113 | continuation.yield(2) 114 | continuation.yield(3) 115 | continuation.finish() 116 | } 117 | 118 | sequence.sink { print($0) } 119 | 120 | // Prints: 121 | // 1 122 | // 2 123 | // 3 124 | ``` 125 | 126 | #### Sink with completion 127 | 128 | ```swift 129 | let sequence = .init { continuation in 130 | continuation.yield(1) 131 | continuation.yield(2) 132 | continuation.yield(3) 133 | continuation.finish(throwing: TestError()) 134 | } 135 | 136 | sequence.sink( 137 | receiveValue: { print("Value: \($0)") }, 138 | receiveCompletion: { print("Complete: \($0)") } 139 | ) 140 | 141 | // Prints: 142 | // Value: 1 143 | // Value: 2 144 | // Value: 3 145 | // Complete: failure(TestError()) 146 | ``` 147 | 148 | ### [AnyAsyncSequenceable](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/anyasyncsequenceable) 149 | 150 | ```swift 151 | let sequence = Just(1) 152 | .map(String.init) 153 | .eraseToAnyAsyncSequenceable() 154 | ``` 155 | 156 | ### [AnyThrowingAsyncSequenceable](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/anythrowingasyncsequenceable) 157 | 158 | ```swift 159 | let stream = Fail(error: TestError.a) 160 | .eraseToAnyThrowingAsyncSequenceable() 161 | ``` 162 | 163 | ### [CatchErrorAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/catcherrorasyncsequence) 164 | 165 | ```swift 166 | let sequence = Fail( 167 | error: TestError() 168 | ) 169 | .catch { error in 170 | Just(-1) 171 | } 172 | 173 | for await value in sequence { 174 | print(value) 175 | } 176 | 177 | // Prints: 178 | // -1 179 | ``` 180 | 181 | ### [ChainAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/chainasyncsequence) 182 | 183 | ```swift 184 | let sequenceA = AsyncStream { continuation in 185 | continuation.yield(1) 186 | continuation.yield(2) 187 | continuation.yield(3) 188 | continuation.finish() 189 | } 190 | 191 | let sequenceB = AsyncStream { continuation in 192 | continuation.yield(4) 193 | continuation.yield(5) 194 | continuation.yield(6) 195 | continuation.finish() 196 | } 197 | 198 | let sequenceC = AsyncStream { continuation in 199 | continuation.yield(7) 200 | continuation.yield(8) 201 | continuation.yield(9) 202 | continuation.finish() 203 | } 204 | 205 | for await value in sequenceA.chain(with: sequenceB).chain(with: sequenceC) { 206 | print(value) 207 | } 208 | 209 | // Prints: 210 | // 1 211 | // 2 212 | // 3 213 | // 4 214 | // 5 215 | // 6 216 | // 7 217 | // 8 218 | // 9 219 | ``` 220 | 221 | ### [CombineLatestAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/combinelatestasyncsequence) 222 | 223 | ```swift 224 | let streamA = .init { continuation in 225 | continuation.yield(1) 226 | continuation.yield(2) 227 | continuation.yield(3) 228 | continuation.yield(4) 229 | continuation.finish() 230 | } 231 | 232 | let streamB = .init { continuation in 233 | continuation.yield(5) 234 | continuation.yield(6) 235 | continuation.yield(7) 236 | continuation.yield(8) 237 | continuation.yield(9) 238 | continuation.finish() 239 | } 240 | 241 | for await value in streamA.combineLatest(streamB) { 242 | print(value) 243 | } 244 | 245 | // Prints: 246 | // (1, 5) 247 | // (2, 6) 248 | // (3, 7) 249 | // (4, 8) 250 | // (4, 9) 251 | ``` 252 | 253 | ### [CombineLatest3AsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/combinelatest3asyncsequence) 254 | 255 | ```swift 256 | let streamA = .init { continuation in 257 | continuation.yield(1) 258 | continuation.yield(2) 259 | continuation.yield(3) 260 | continuation.yield(4) 261 | continuation.finish() 262 | } 263 | 264 | let streamB = .init { continuation in 265 | continuation.yield(5) 266 | continuation.yield(6) 267 | continuation.yield(7) 268 | continuation.yield(8) 269 | continuation.yield(9) 270 | continuation.finish() 271 | } 272 | 273 | let streamC = .init { continuation in 274 | continuation.yield(10) 275 | continuation.yield(11) 276 | continuation.finish() 277 | } 278 | 279 | for await value in streamA.combineLatest(streamB, streamC) { 280 | print(value) 281 | } 282 | 283 | // Prints: 284 | // (1, 5, 10) 285 | // (2, 6, 11) 286 | // (3, 7, 11) 287 | // (4, 8, 11) 288 | // (4, 9, 11) 289 | ``` 290 | 291 | ### [CurrentElementAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/currentelementasyncsequence) 292 | 293 | ```swift 294 | let sequence = CurrentElementAsyncSequence(0) 295 | print(await sequence.element) 296 | 297 | await stream.yield(1) 298 | print(await sequence.element) 299 | 300 | await stream.yield(2) 301 | await stream.yield(3) 302 | await stream.yield(4) 303 | print(await sequence.element) 304 | 305 | // Prints: 306 | // 0 307 | // 1 308 | // 4 309 | ``` 310 | 311 | ### [DebounceAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/debounceasyncsequence) 312 | 313 | ```swift 314 | let stream = AsyncStream { continuation in 315 | continuation.yield(0) 316 | try? await Task.sleep(nanoseconds: 200_000_000) 317 | continuation.yield(1) 318 | try? await Task.sleep(nanoseconds: 200_000_000) 319 | continuation.yield(2) 320 | continuation.yield(3) 321 | continuation.yield(4) 322 | continuation.yield(5) 323 | continuation.finish() 324 | } 325 | 326 | for element in try await self.stream.debounce(for: 0.1) { 327 | print(element) 328 | } 329 | 330 | // Prints: 331 | // 0 332 | // 1 333 | // 5 334 | ``` 335 | 336 | ### [DelayAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/delayasyncsequence) 337 | 338 | ```swift 339 | let stream = AsyncStream { continuation in 340 | continuation.yield(0) 341 | continuation.yield(1) 342 | continuation.yield(2) 343 | continuation.finish() 344 | } 345 | 346 | let start = Date.now 347 | for element in try await self.stream.delay(for: 0.5) { 348 | print("\(element) - \(Date.now.timeIntervalSince(start))") 349 | } 350 | 351 | // Prints: 352 | // 0 - 0.5 353 | // 1 - 1.0 354 | // 2 - 1.5 355 | >>>>>>> main 356 | ``` 357 | 358 | ### [Empty](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/empty) 359 | 360 | ```swift 361 | Empty().sink( 362 | receiveValue: { print($0) }, 363 | receiveCompletion: { completion in 364 | switch completion { 365 | case .finished: 366 | print("Finished") 367 | case .failure: 368 | print("Failed") 369 | } 370 | } 371 | ) 372 | 373 | // Prints: 374 | // Finished 375 | ``` 376 | 377 | ### [Fail](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/fail) 378 | 379 | ```swift 380 | let stream = Fail(error: TestError()) 381 | 382 | do { 383 | for try await value in stream { 384 | print(value) 385 | } 386 | } catch { 387 | print("Error!") 388 | } 389 | 390 | // Prints: 391 | // Error! 392 | ``` 393 | 394 | ### [Just](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/just) 395 | 396 | ```swift 397 | let stream = Just(1) 398 | 399 | for await value in stream { 400 | print(value) 401 | } 402 | 403 | // Prints: 404 | // 1 405 | ``` 406 | 407 | ### [MergeAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/mergeasyncsequence) 408 | 409 | ```swift 410 | let streamA = .init { continuation in 411 | continuation.yield(1) 412 | continuation.yield(2) 413 | continuation.yield(3) 414 | continuation.yield(4) 415 | continuation.finish() 416 | } 417 | 418 | let streamB = .init { continuation in 419 | continuation.yield(5) 420 | continuation.yield(6) 421 | continuation.yield(7) 422 | continuation.yield(8) 423 | continuation.yield(9) 424 | continuation.finish() 425 | } 426 | 427 | for await value in streamA.merge(with: streamB) { 428 | print(value) 429 | } 430 | 431 | // Prints: 432 | // 1 433 | // 5 434 | // 2 435 | // 6 436 | // 3 437 | // 7 438 | // 4 439 | // 8 440 | // 9 441 | ``` 442 | 443 | ### [Merge3AsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/merge3asyncsequence) 444 | 445 | ```swift 446 | let streamA = .init { continuation in 447 | continuation.yield(1) 448 | continuation.yield(4) 449 | continuation.finish() 450 | } 451 | 452 | let streamB = .init { continuation in 453 | continuation.yield(2) 454 | continuation.finish() 455 | } 456 | 457 | let streamC = .init { continuation in 458 | continuation.yield(3) 459 | continuation.finish() 460 | } 461 | 462 | for await value in self.streamA.merge(with: self.streamB, self.streamC) { 463 | print(value) 464 | } 465 | 466 | // Prints: 467 | // 1 468 | // 2 469 | // 3 470 | // 4 471 | ``` 472 | 473 | ### [NotificationCenterAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/notificationcenterasyncsequence) 474 | 475 | ```swift 476 | let sequence = NotificationCenter.default.sequence(for: UIDevice.orientationDidChangeNotification) 477 | 478 | for await element in sequence { 479 | print(element) 480 | } 481 | 482 | ``` 483 | 484 | ### [PassthroughAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/passthroughasyncsequence) 485 | 486 | ```swift 487 | let sequence = PassthroughAsyncSequence() 488 | sequence.yield(0) 489 | sequence.yield(1) 490 | sequence.yield(2) 491 | sequence.finish() 492 | 493 | for await value in sequence { 494 | print(value) 495 | } 496 | 497 | // Prints: 498 | // 0 499 | // 1 500 | // 2 501 | ``` 502 | 503 | ### [RemoveDuplicatesAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/removeduplicatesasyncsequence) 504 | 505 | ```swift 506 | let stream = .init { continuation in 507 | continuation.yield(1) 508 | continuation.yield(1) 509 | continuation.yield(2) 510 | continuation.yield(3) 511 | continuation.finish() 512 | } 513 | 514 | for await value in stream.removeDuplicates() { 515 | print(value) 516 | } 517 | 518 | // Prints: 519 | // 1 520 | // 2 521 | // 3 522 | ``` 523 | 524 | ### [ReplaceErrorAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/replaceerrorasyncsequence) 525 | 526 | ```swift 527 | let sequence = Fail( 528 | error: TestError() 529 | ) 530 | .replaceError(with: 0) 531 | 532 | for await value in stream { 533 | print(value) 534 | } 535 | 536 | // Prints: 537 | // 0 538 | ``` 539 | 540 | ### [SequenceAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/sequenceasyncsequence) 541 | 542 | ```swift 543 | let sequence = [0, 1, 2, 3].async 544 | 545 | for await value in sequence { 546 | print(value) 547 | } 548 | 549 | // Prints: 550 | // 1 551 | // 2 552 | // 3 553 | ``` 554 | 555 | ### [SharedAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/sharedasyncsequence) 556 | 557 | ```swift 558 | let values = [ 559 | "a", 560 | "ab", 561 | "abc", 562 | "abcd" 563 | ] 564 | 565 | let stream = AsyncStream { continuation in 566 | for value in values { 567 | continuation.yield(value) 568 | } 569 | continuation.finish() 570 | } 571 | .shared() 572 | 573 | Task { 574 | let values = try await self.stream.collect() 575 | // ... 576 | } 577 | 578 | Task.detached { 579 | let values = try await self.stream.collect() 580 | // ... 581 | } 582 | 583 | let values = try await self.stream.collect() 584 | // ... 585 | ``` 586 | 587 | ### [ThrottleAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/throttleasyncsequence) 588 | 589 | ```swift 590 | let stream = AsyncStream { continuation in 591 | continuation.yield(0) 592 | try? await Task.sleep(nanoseconds: 100_000_000) 593 | continuation.yield(1) 594 | try? await Task.sleep(nanoseconds: 100_000_000) 595 | continuation.yield(2) 596 | continuation.yield(3) 597 | continuation.yield(4) 598 | continuation.yield(5) 599 | continuation.finish() 600 | } 601 | 602 | for element in try await self.stream.throttle(for: 0.05, latest: true) { 603 | print(element) 604 | } 605 | 606 | // Prints: 607 | // 0 608 | // 1 609 | // 2 610 | // 5 611 | ``` 612 | 613 | ### [ThrowingPassthroughAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/throwingpassthroughasyncsequence) 614 | 615 | ```swift 616 | let sequence = ThrowingPassthroughAsyncSequence() 617 | sequence.yield(0) 618 | sequence.yield(1) 619 | sequence.yield(2) 620 | sequence.finish(throwing: TestError()) 621 | 622 | do { 623 | for try await value in sequence { 624 | print(value) 625 | } 626 | } catch { 627 | print("Error!") 628 | } 629 | 630 | // Prints: 631 | // 0 632 | // 1 633 | // 2 634 | // Error! 635 | ``` 636 | 637 | ### [TimerAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/timerasyncsequence) 638 | 639 | ```swift 640 | let sequence = TimerAsyncSequence(interval: 1) 641 | 642 | let start = Date.now 643 | for element in await sequence { 644 | print(element) 645 | } 646 | 647 | // Prints: 648 | // 2022-03-19 20:49:30 +0000 649 | // 2022-03-19 20:49:31 +0000 650 | // 2022-03-19 20:49:32 +0000 651 | ``` 652 | 653 | ### [ZipAsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/zipasyncsequence) 654 | 655 | ```swift 656 | let streamA = .init { continuation in 657 | continuation.yield(1) 658 | continuation.yield(2) 659 | continuation.finish() 660 | } 661 | 662 | let streamB = .init { continuation in 663 | continuation.yield(5) 664 | continuation.yield(6) 665 | continuation.yield(7) 666 | continuation.finish() 667 | } 668 | 669 | for await value in streamA.zip(streamB) { 670 | print(value) 671 | } 672 | 673 | // Prints: 674 | // (1, 5) 675 | // (2, 6) 676 | ``` 677 | 678 | ### [Zip3AsyncSequence](https://swiftpackageindex.com/reddavis/asynchrone/main/documentation/asynchrone/zip3asyncsequence) 679 | 680 | ```swift 681 | let streamA = .init { continuation in 682 | continuation.yield(1) 683 | continuation.yield(2) 684 | continuation.finish() 685 | } 686 | 687 | let streamB = .init { continuation in 688 | continuation.yield(5) 689 | continuation.yield(6) 690 | continuation.yield(7) 691 | continuation.finish() 692 | } 693 | 694 | let streamC = .init { continuation in 695 | continuation.yield(8) 696 | continuation.yield(9) 697 | continuation.finish() 698 | } 699 | 700 | for await value in streamA.zip(streamB, streamC) { 701 | print(value) 702 | } 703 | 704 | // Prints: 705 | // (1, 5, 8) 706 | // (2, 6, 9) 707 | ``` 708 | 709 | ## Other libraries 710 | 711 | - [Papyrus](https://github.com/reddavis/Papyrus) - Papyrus aims to hit the sweet spot between saving raw API responses to the file system and a fully fledged database like Realm. 712 | - [Validate](https://github.com/reddavis/Validate) - A property wrapper that can validate the property it wraps. 713 | - [Kyu](https://github.com/reddavis/Kyu) - A persistent queue system in Swift. 714 | - [FloatingLabelTextFieldStyle](https://github.com/reddavis/FloatingLabelTextFieldStyle) - A floating label style for SwiftUI's TextField. 715 | - [Panel](https://github.com/reddavis/Panel) - A panel component similar to the iOS Airpod battery panel. 716 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Common/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 | @rethrows 13 | protocol _ErrorMechanism { 14 | associatedtype Output 15 | func get() throws -> Output 16 | } 17 | 18 | extension _ErrorMechanism { 19 | func _rethrowError() rethrows -> Never { 20 | _ = try _rethrowGet() 21 | fatalError("Materialized error without being in a throwing context") 22 | } 23 | 24 | func _rethrowGet() rethrows -> Output { 25 | try get() 26 | } 27 | } 28 | 29 | extension Result: _ErrorMechanism { } 30 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Common/RethrowingAccessor.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | @rethrows 4 | protocol RethrowingAccessor { 5 | associatedtype T 6 | func _value() throws -> T 7 | } 8 | 9 | extension RethrowingAccessor { 10 | func _forceRethrowError() rethrows { 11 | _ = try _retrowValue() 12 | fatalError("No error") 13 | } 14 | 15 | func _retrowValue() rethrows -> T { 16 | try self._value() 17 | } 18 | } 19 | 20 | // MARK: Result 21 | 22 | extension Result: RethrowingAccessor { 23 | func _value() throws -> Success { 24 | try self.get() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Documentation.docc/Asynchrone.md: -------------------------------------------------------------------------------- 1 | # ``Asynchrone`` 2 | 3 | Extensions and additions to AsyncSequence, AsyncStream and AsyncThrowingStream. 4 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Extensions/AsyncSequence+Extension.swift: -------------------------------------------------------------------------------- 1 | extension AsyncSequence { 2 | /// Assigns each element from an async sequence to a property on an object. 3 | /// 4 | /// ```swift 5 | /// class MyClass { 6 | /// var value: Int = 0 { 7 | /// didSet { print("Set to \(self.value)") } 8 | /// } 9 | /// } 10 | /// 11 | /// 12 | /// let sequence = AsyncStream { continuation in 13 | /// continuation.yield(1) 14 | /// continuation.yield(2) 15 | /// continuation.yield(3) 16 | /// continuation.finish() 17 | /// } 18 | /// 19 | /// let object = MyClass() 20 | /// sequence.assign(to: \.value, on: object) 21 | /// 22 | /// // Prints: 23 | /// // Set to 1 24 | /// // Set to 2 25 | /// // Set to 3 26 | /// ``` 27 | /// 28 | /// - Parameters: 29 | /// - keyPath: A key path to indicate the property to be assign. 30 | /// - object: The object that contains the property. 31 | /// - Returns: A `Task`. It is not required to keep reference to the task, 32 | /// but it does give the ability to cancel the assign by calling `cancel()`. 33 | @discardableResult 34 | public func assign( 35 | to keyPath: ReferenceWritableKeyPath, 36 | on object: Root 37 | ) rethrows -> Task where Self: Sendable, Root: Sendable { 38 | Task { 39 | for try await element in self { 40 | object[keyPath: keyPath] = element 41 | } 42 | } 43 | } 44 | 45 | /// The first element of the sequence, if there is one. 46 | public func first() async rethrows -> Element? { 47 | try await self.first { _ in 48 | true 49 | } 50 | } 51 | 52 | /// The last element of the sequence, if there is one. 53 | public func last() async rethrows -> Element? { 54 | var latestElement: Element? 55 | for try await element in self { 56 | latestElement = element 57 | } 58 | 59 | return latestElement 60 | } 61 | 62 | /// Collect elements from a sequence. 63 | /// 64 | /// ```swift 65 | /// // Collect all elements. 66 | /// var values = await self.sequence.collect() 67 | /// print(values) 68 | /// 69 | /// // Prints: 70 | /// // [1, 2, 3] 71 | /// 72 | /// // Collect only 2 elements. 73 | /// values = await self.sequence.collect(2) 74 | /// print(values) 75 | /// 76 | /// // Prints: 77 | /// // [1, 2] 78 | /// ``` 79 | /// 80 | /// - Parameter numberOfElements: The number of elements to collect. By default 81 | /// this is `nil` which indicates all elements will be collected. If the number of elements 82 | /// in the sequence is less than the number of elements requested, then all the elements will 83 | /// be collected. 84 | /// - Returns: Returns: An array of all elements. 85 | public func collect(_ numberOfElements: Int? = .none) async rethrows -> [Element] { 86 | var results: [Element] = [] 87 | for try await element in self { 88 | results.append(element) 89 | 90 | if let numberOfElements = numberOfElements, 91 | results.count >= numberOfElements { 92 | break 93 | } 94 | } 95 | 96 | return results 97 | } 98 | 99 | /// Consume the async sequence and pass the element's to a closure. 100 | /// 101 | /// ```swift 102 | /// let sequence = .init { continuation in 103 | /// continuation.yield(1) 104 | /// continuation.yield(2) 105 | /// continuation.yield(3) 106 | /// continuation.finish() 107 | /// } 108 | /// 109 | /// sequence.sink { print($0) } 110 | /// 111 | /// // Prints: 112 | /// // 1 113 | /// // 2 114 | /// // 3 115 | /// ``` 116 | /// - Parameters: 117 | /// - priority: The priority of the task. Pass nil to use the priority from `Task.currentPriority`. 118 | /// - receiveValue: The closure to execute on receipt of a value. 119 | /// - Returns: A task instance. 120 | @discardableResult 121 | public func sink( 122 | priority: TaskPriority? = nil, 123 | receiveValue: @Sendable @escaping (Element) async -> Void 124 | ) -> Task where Self: Sendable { 125 | Task(priority: priority) { 126 | for try await element in self { 127 | await receiveValue(element) 128 | try Task.checkCancellation() 129 | } 130 | } 131 | } 132 | 133 | /// Consume the async sequence and pass the element's and it's completion 134 | /// state to two closures. 135 | /// 136 | /// ```swift 137 | /// let sequence = .init { continuation in 138 | /// continuation.yield(1) 139 | /// continuation.yield(2) 140 | /// continuation.yield(3) 141 | /// continuation.finish(throwing: TestError()) 142 | /// } 143 | /// 144 | /// sequence.sink( 145 | /// receiveValue: { print("Value: \($0)") }, 146 | /// receiveCompletion: { print("Complete: \($0)") } 147 | /// ) 148 | /// 149 | /// // Prints: 150 | /// // Value: 1 151 | /// // Value: 2 152 | /// // Value: 3 153 | /// // Complete: failure(TestError()) 154 | /// ``` 155 | /// - Parameters: 156 | /// - priority: The priority of the task. Pass nil to use the priority from `Task.currentPriority`. 157 | /// - receiveValue: The closure to execute on receipt of a value. 158 | /// - receiveCompletion: The closure to execute on completion. 159 | /// - Returns: A task instance. 160 | @discardableResult 161 | public func sink( 162 | priority: TaskPriority? = nil, 163 | receiveValue: @Sendable @escaping (Element) async -> Void, 164 | receiveCompletion: @Sendable @escaping (AsyncSequenceCompletion) async -> Void 165 | ) -> Task where Self: Sendable { 166 | Task(priority: priority) { 167 | do { 168 | for try await element in self { 169 | await receiveValue(element) 170 | try Task.checkCancellation() 171 | } 172 | 173 | try Task.checkCancellation() 174 | await receiveCompletion(.finished) 175 | } catch { 176 | await receiveCompletion(.failure(error)) 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Extensions/AsyncStream+Extension.swift: -------------------------------------------------------------------------------- 1 | extension AsyncStream { 2 | // MARK: Initialization 3 | 4 | /// Construct a AsyncStream buffering given an Element type. 5 | /// 6 | /// - Parameter elementType: The type the AsyncStream will produce. 7 | /// - Parameter maxBufferedElements: The maximum number of elements to 8 | /// hold in the buffer past any checks for continuations being resumed. 9 | /// - Parameter build: The work associated with yielding values to the 10 | /// AsyncStream. 11 | /// 12 | /// The maximum number of pending elements limited by dropping the oldest 13 | /// value when a new value comes in if the buffer would excede the limit 14 | /// placed upon it. By default this limit is unlimited. 15 | /// 16 | /// The build closure passes in a Continuation which can be used in 17 | /// concurrent contexts. It is thread safe to send and finish; all calls are 18 | /// to the continuation are serialized, however calling this from multiple 19 | /// concurrent contexts could result in out of order delivery. 20 | public init( 21 | _ elementType: Element.Type = Element.self, 22 | bufferingPolicy limit: AsyncStream.Continuation.BufferingPolicy = .unbounded, 23 | _ build: @escaping (AsyncStream.Continuation) async -> Void 24 | ) { 25 | self = AsyncStream(elementType, bufferingPolicy: limit) { continuation in 26 | let task = Task { 27 | await build(continuation) 28 | } 29 | 30 | continuation.onTermination = { _ in 31 | task.cancel() 32 | } 33 | } 34 | } 35 | } 36 | 37 | 38 | 39 | // MARK: AsyncStream.Continuation 40 | 41 | extension AsyncStream.Continuation { 42 | /// Yield the provided value and then finish the stream. 43 | /// - Parameter value: The value to yield to the stream. 44 | public func finish(with value: Element) { 45 | self.yield(value) 46 | self.finish() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Extensions/AsyncThrowingStream+Extension.swift: -------------------------------------------------------------------------------- 1 | extension AsyncThrowingStream { 2 | // MARK: Initialization 3 | 4 | /// Construct a AsyncThrowingStream buffering given an Element type. 5 | /// 6 | /// - Parameter elementType: The type the AsyncThrowingStream will produce. 7 | /// - Parameter maxBufferedElements: The maximum number of elements to 8 | /// hold in the buffer past any checks for continuations being resumed. 9 | /// - Parameter build: The work associated with yielding values to the 10 | /// AsyncThrowingStream. 11 | /// 12 | /// The maximum number of pending elements limited by dropping the oldest 13 | /// value when a new value comes in if the buffer would excede the limit 14 | /// placed upon it. By default this limit is unlimited. 15 | /// 16 | /// The build closure passes in a Continuation which can be used in 17 | /// concurrent contexts. It is thread safe to send and finish; all calls are 18 | /// to the continuation are serialized, however calling this from multiple 19 | /// concurrent contexts could result in out of order delivery. 20 | public init( 21 | _ elementType: Element.Type = Element.self, 22 | bufferingPolicy limit: AsyncThrowingStream.Continuation.BufferingPolicy = .unbounded, 23 | _ build: @Sendable @escaping (AsyncThrowingStream.Continuation) async -> Void 24 | ) where Failure == Error { 25 | self = AsyncThrowingStream(elementType, bufferingPolicy: limit) { continuation in 26 | let task = Task { 27 | await build(continuation) 28 | } 29 | 30 | continuation.onTermination = { _ in 31 | task.cancel() 32 | } 33 | } 34 | } 35 | } 36 | 37 | 38 | // MARK: AsyncThrowingStream.Continuation 39 | 40 | extension AsyncThrowingStream.Continuation { 41 | /// Yield the provided value and then finish the stream. 42 | /// - Parameter value: The value to yield to the stream. 43 | public func finish(with value: Element) { 44 | self.yield(value) 45 | self.finish() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Extensions/Task+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Task where Success == Never, Failure == Never { 4 | 5 | /// Suspends the current task for at least the given duration in seconds. 6 | /// 7 | /// If the task is canceled before the time ends, this function throws CancellationError. 8 | /// This function doesn’t block the underlying thread. 9 | /// - Parameter duration: The number of seconds to suspend the current task for. 10 | public static func sleep(seconds duration: TimeInterval) async throws { 11 | try await Task.sleep(nanoseconds: UInt64(duration.asNanoseconds)) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Extensions/TimeInterval+Extension.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension TimeInterval { 4 | var asNanoseconds: TimeInterval { 5 | self * 1_000_000_000 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/AnyAsyncSequenceable.swift: -------------------------------------------------------------------------------- 1 | /// An async sequence that performs type erasure by wrapping another async sequence. 2 | /// 3 | /// If the async sequence that you wish to type erase can throw, then use `AnyThrowingAsyncSequenceable`. 4 | public struct AnyAsyncSequenceable: AsyncSequence, Sendable { 5 | private var _makeAsyncIterator: @Sendable () -> Iterator 6 | 7 | // MARK: Initialization 8 | 9 | /// Creates a type erasing async sequence. 10 | /// - Parameters: 11 | /// - sequence: The async sequence to type erase. 12 | public init(_ sequence: T) where T: AsyncSequence, T.Element == Element, T: Sendable { 13 | self._makeAsyncIterator = { Iterator(sequence.makeAsyncIterator()) } 14 | } 15 | 16 | /// Creates an optional type erasing async sequence. 17 | /// - Parameters: 18 | /// - sequence: An optional async sequence to type erase. 19 | public init?(_ asyncSequence: T?) where T: AsyncSequence, T.Element == Element, T: Sendable { 20 | guard let asyncSequence = asyncSequence else { return nil } 21 | self = .init(asyncSequence) 22 | } 23 | 24 | // MARK: AsyncSequence 25 | 26 | public func makeAsyncIterator() -> Iterator { 27 | self._makeAsyncIterator() 28 | } 29 | } 30 | 31 | // MARK: Iterator 32 | 33 | extension AnyAsyncSequenceable { 34 | public struct Iterator: AsyncIteratorProtocol { 35 | private var iterator: any AsyncIteratorProtocol 36 | 37 | // MARK: Initialization 38 | 39 | init(_ iterator: T) where T: AsyncIteratorProtocol, T.Element == Element { 40 | self.iterator = iterator 41 | } 42 | 43 | // MARK: AsyncIteratorProtocol 44 | 45 | public mutating func next() async -> Element? { 46 | // NOTE: When `AsyncSequence`, `AsyncIteratorProtocol` get their Element as 47 | // their primary associated type we won't need the casting. 48 | // https://github.com/apple/swift-evolution/blob/main/proposals/0358-primary-associated-types-in-stdlib.md#alternatives-considered 49 | 50 | // NOTE: Doing `try? await self.iterator.next() as? Element` makes some weird shit happen 51 | // that I don't quite know how to explain. 52 | // 53 | // This is why this is split into two statements. 54 | guard let element = try? await self.iterator.next() else { return nil } 55 | return element as? Element 56 | } 57 | } 58 | } 59 | 60 | // MARK: Erasure 61 | 62 | extension AsyncSequence { 63 | /// Creates a type erasing async sequence. 64 | /// 65 | /// If the async sequence that you wish to type erase can throw, 66 | /// then use `eraseToAnyThrowingAsyncSequenceable()`. 67 | /// - Returns: A typed erased async sequence. 68 | public func eraseToAnyAsyncSequenceable() -> AnyAsyncSequenceable where Self: Sendable { 69 | .init(self) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/AnyThrowingAsyncSequenceable.swift: -------------------------------------------------------------------------------- 1 | /// A throwing async sequence that performs type erasure by wrapping another throwing async sequence. 2 | /// 3 | /// If the async sequence that you wish to type erase doesn't throw, then use `AnyAsyncSequenceable`. 4 | public struct AnyThrowingAsyncSequenceable: AsyncSequence, Sendable { 5 | private var _makeAsyncIterator: @Sendable () -> Iterator 6 | 7 | // MARK: Initialization 8 | 9 | /// Creates a type erasing async sequence. 10 | /// - Parameters: 11 | /// - sequence: The async sequence to type erase. 12 | public init(_ sequence: T) where T: AsyncSequence, T: Sendable, T.Element == Element { 13 | self._makeAsyncIterator = { Iterator(sequence.makeAsyncIterator()) } 14 | } 15 | 16 | /// Creates an optional type erasing async sequence. 17 | /// - Parameters: 18 | /// - sequence: An optional async sequence to type erase. 19 | public init?(_ asyncSequence: T?) where T: AsyncSequence, T: Sendable, T.Element == Element { 20 | guard let asyncSequence = asyncSequence else { return nil } 21 | self = .init(asyncSequence) 22 | } 23 | 24 | // MARK: AsyncSequence 25 | 26 | public func makeAsyncIterator() -> Iterator { 27 | self._makeAsyncIterator() 28 | } 29 | } 30 | 31 | // MARK: Iterator 32 | 33 | extension AnyThrowingAsyncSequenceable { 34 | public struct Iterator: AsyncIteratorProtocol { 35 | private var iterator: any AsyncIteratorProtocol 36 | 37 | // MARK: Initialization 38 | 39 | init(_ iterator: T) where T: AsyncIteratorProtocol, T.Element == Element { 40 | self.iterator = iterator 41 | } 42 | 43 | // MARK: AsyncIteratorProtocol 44 | 45 | public mutating func next() async throws -> Element? { 46 | // NOTE: When `AsyncSequence`, `AsyncIteratorProtocol` get their Element as 47 | // their primary associated type we won't need the casting. 48 | // https://github.com/apple/swift-evolution/blob/main/proposals/0358-primary-associated-types-in-stdlib.md#alternatives-considered 49 | try await self.iterator.next() as? Element 50 | } 51 | } 52 | } 53 | 54 | // MARK: Erasure 55 | 56 | extension AsyncSequence { 57 | 58 | /// Creates a throwing type erasing async sequence. 59 | /// 60 | /// If the async sequence that you wish to type erase deson't throw, 61 | /// then use `eraseToAnyAsyncSequenceable()`. 62 | /// - Returns: A typed erased async sequence. 63 | public func eraseToAnyThrowingAsyncSequenceable() -> AnyThrowingAsyncSequenceable where Self: Sendable { 64 | .init(self) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/AsyncSequenceCompletion.swift: -------------------------------------------------------------------------------- 1 | /// Describes how an async sequence has completed. 2 | public enum AsyncSequenceCompletion { 3 | /// The async sequence finished normally. 4 | case finished 5 | 6 | /// The async sequence stopped emitting elements due to 7 | /// the indicated error. 8 | case failure(Failure) 9 | } 10 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/CatchErrorAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// Catches any errors in the async sequence and replaces it 2 | /// with the provided async sequence. 3 | /// 4 | /// ```swift 5 | /// let sequence = Fail( 6 | /// error: TestError() 7 | /// ) 8 | /// .catch { error in 9 | /// Just(-1) 10 | /// } 11 | /// 12 | /// for await value in sequence { 13 | /// print(value) 14 | /// } 15 | /// 16 | /// // Prints: 17 | /// // -1 18 | /// ``` 19 | public struct CatchErrorAsyncSequence: AsyncSequence 20 | where 21 | Base: AsyncSequence, 22 | NewAsyncSequence: AsyncSequence, 23 | Base.Element == NewAsyncSequence.Element { 24 | /// The kind of elements streamed. 25 | public typealias Element = Base.Element 26 | 27 | // Private 28 | private let base: Base 29 | private let handler: @Sendable (Error) -> NewAsyncSequence 30 | private var iterator: Base.AsyncIterator 31 | private var caughtIterator: NewAsyncSequence.AsyncIterator? 32 | 33 | // MARK: Initialization 34 | 35 | /// Creates an async sequence that replaces any errors in the sequence with a provided element. 36 | /// - Parameters: 37 | /// - base: The async sequence in which this sequence receives it's elements. 38 | /// - output: The element with which to replace errors from the base async sequence. 39 | public init( 40 | base: Base, 41 | handler: @Sendable @escaping (Error) -> NewAsyncSequence 42 | ) { 43 | self.base = base 44 | self.handler = handler 45 | self.iterator = base.makeAsyncIterator() 46 | } 47 | 48 | // MARK: AsyncSequence 49 | 50 | /// Creates an async iterator that emits elements of this async sequence. 51 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 52 | public func makeAsyncIterator() -> Self { 53 | .init(base: self.base, handler: self.handler) 54 | } 55 | } 56 | 57 | extension CatchErrorAsyncSequence: Sendable 58 | where 59 | Base: Sendable, 60 | NewAsyncSequence: Sendable, 61 | Base.AsyncIterator: Sendable, 62 | NewAsyncSequence.AsyncIterator: Sendable {} 63 | 64 | // MARK: AsyncIteratorProtocol 65 | 66 | extension CatchErrorAsyncSequence: AsyncIteratorProtocol { 67 | public mutating func next() async -> Element? { 68 | if self.caughtIterator != nil { 69 | return try? await self.caughtIterator?.next() 70 | } 71 | 72 | do { 73 | return try await self.iterator.next() 74 | } catch { 75 | self.caughtIterator = self.handler(error).makeAsyncIterator() 76 | return await self.next() 77 | } 78 | } 79 | } 80 | 81 | // MARK: Catch error 82 | 83 | extension AsyncSequence { 84 | /// Catches any errors in the async sequence and replaces it 85 | /// with the provided async sequence. 86 | /// 87 | /// ```swift 88 | /// let sequence = Fail( 89 | /// error: TestError() 90 | /// ) 91 | /// .catch { error in 92 | /// Just(-1) 93 | /// } 94 | /// 95 | /// for await value in sequence { 96 | /// print(value) 97 | /// } 98 | /// 99 | /// // Prints: 100 | /// // -1 101 | /// ``` 102 | /// 103 | /// - Parameter handler: A closure that takes an Error and returns a new async sequence. 104 | /// - Returns: A `CatchErrorAsyncSequence` instance. 105 | public func `catch`( 106 | _ handler: @Sendable @escaping (Error) -> S 107 | ) -> CatchErrorAsyncSequence where S: AsyncSequence, S.Element == Self.Element { 108 | .init(base: self, handler: handler) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/ChainAsyncSequenceable.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that chains two async sequences. 2 | /// 3 | /// The combined sequence first emits the all the values from the first sequence 4 | /// and then emits all values from the second. 5 | /// 6 | /// ```swift 7 | /// let sequenceA = AsyncStream { continuation in 8 | /// continuation.yield(1) 9 | /// continuation.yield(2) 10 | /// continuation.yield(3) 11 | /// continuation.finish() 12 | /// } 13 | /// 14 | /// let sequenceB = AsyncStream { continuation in 15 | /// continuation.yield(4) 16 | /// continuation.yield(5) 17 | /// continuation.yield(6) 18 | /// continuation.finish() 19 | /// } 20 | /// 21 | /// let sequenceC = AsyncStream { continuation in 22 | /// continuation.yield(7) 23 | /// continuation.yield(8) 24 | /// continuation.yield(9) 25 | /// continuation.finish() 26 | /// } 27 | /// 28 | /// for await value in sequenceA <> sequenceB <> sequenceC { 29 | /// print(value) 30 | /// } 31 | /// 32 | /// // Prints: 33 | /// // 1 34 | /// // 2 35 | /// // 3 36 | /// // 4 37 | /// // 5 38 | /// // 6 39 | /// // 7 40 | /// // 8 41 | /// // 9 42 | /// ``` 43 | public struct ChainAsyncSequence: AsyncSequence where P.Element == Q.Element { 44 | /// The kind of elements streamed. 45 | public typealias Element = P.Element 46 | 47 | // Private 48 | private let p: P 49 | private let q: Q 50 | 51 | private var iteratorP: P.AsyncIterator 52 | private var iteratorQ: Q.AsyncIterator 53 | 54 | // MARK: Initialization 55 | 56 | /// Creates an async sequence that combines the two async sequence. 57 | /// - Parameters: 58 | /// - p: The first async sequence. 59 | /// - q: The second async sequence. 60 | public init( 61 | _ p: P, 62 | _ q: Q 63 | ) { 64 | self.p = p 65 | self.iteratorP = p.makeAsyncIterator() 66 | 67 | self.q = q 68 | self.iteratorQ = q.makeAsyncIterator() 69 | } 70 | 71 | // MARK: AsyncSequence 72 | 73 | /// Creates an async iterator that emits elements of this async sequence. 74 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 75 | public func makeAsyncIterator() -> Self { 76 | .init(self.p, self.q) 77 | } 78 | } 79 | 80 | extension ChainAsyncSequence: Sendable 81 | where 82 | P: Sendable, 83 | P.AsyncIterator: Sendable, 84 | Q: Sendable, 85 | Q.AsyncIterator: Sendable {} 86 | 87 | // MARK: AsyncIteratorProtocol 88 | 89 | extension ChainAsyncSequence: AsyncIteratorProtocol { 90 | public mutating func next() async rethrows -> Element? { 91 | if let element = try await self.iteratorP.next() { 92 | return element 93 | } else { 94 | return try await self.iteratorQ.next() 95 | } 96 | } 97 | } 98 | 99 | // MARK: Chain 100 | 101 | extension AsyncSequence { 102 | /// An asynchronous sequence that chains two async sequences. 103 | /// 104 | /// The combined sequence first emits the all the values from the first sequence 105 | /// and then emits all values from the second. 106 | /// 107 | /// ```swift 108 | /// let sequenceA = AsyncStream { continuation in 109 | /// continuation.yield(1) 110 | /// continuation.yield(2) 111 | /// continuation.yield(3) 112 | /// continuation.finish() 113 | /// } 114 | /// 115 | /// let sequenceB = AsyncStream { continuation in 116 | /// continuation.yield(4) 117 | /// continuation.yield(5) 118 | /// continuation.yield(6) 119 | /// continuation.finish() 120 | /// } 121 | /// 122 | /// let sequenceC = AsyncStream { continuation in 123 | /// continuation.yield(7) 124 | /// continuation.yield(8) 125 | /// continuation.yield(9) 126 | /// continuation.finish() 127 | /// } 128 | /// 129 | /// for await value in sequenceA.chain(with: sequenceB).chain(with: sequenceC) { 130 | /// print(value) 131 | /// } 132 | /// 133 | /// // Prints: 134 | /// // 1 135 | /// // 2 136 | /// // 3 137 | /// // 4 138 | /// // 5 139 | /// // 6 140 | /// // 7 141 | /// // 8 142 | /// // 9 143 | /// ``` 144 | /// - Parameters: 145 | /// - lhs: The first async sequence to iterate through. 146 | /// - rhs: The second async sequence to iterate through. 147 | /// - Returns: A async sequence chains the two sequences. 148 | public func chain

(with sequence: P) -> ChainAsyncSequence where P: AsyncSequence { 149 | .init(self, sequence) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/CombineLatest3AsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that combines three async sequences. 2 | /// 3 | /// The combined sequence emits a tuple of the most-recent elements from each sequence 4 | /// when any of them emit a value. 5 | /// 6 | /// If one sequence never emits a value this sequence will finish. 7 | /// 8 | /// ```swift 9 | /// let streamA = .init { continuation in 10 | /// continuation.yield(1) 11 | /// continuation.yield(2) 12 | /// continuation.yield(3) 13 | /// continuation.yield(4) 14 | /// continuation.finish() 15 | /// } 16 | /// 17 | /// let streamB = .init { continuation in 18 | /// continuation.yield(5) 19 | /// continuation.yield(6) 20 | /// continuation.yield(7) 21 | /// continuation.yield(8) 22 | /// continuation.yield(9) 23 | /// continuation.finish() 24 | /// } 25 | /// 26 | /// let streamC = .init { continuation in 27 | /// continuation.yield(10) 28 | /// continuation.yield(11) 29 | /// continuation.finish() 30 | /// } 31 | /// 32 | /// for await value in streamA.combineLatest(streamB, streamC) { 33 | /// print(value) 34 | /// } 35 | /// 36 | /// // Prints: 37 | /// // (1, 5, 10) 38 | /// // (2, 6, 11) 39 | /// // (3, 7, 11) 40 | /// // (4, 8, 11) 41 | /// // (4, 9, 11) 42 | /// ``` 43 | public struct CombineLatest3AsyncSequence: AsyncSequence { 44 | /// The kind of elements streamed. 45 | public typealias Element = (P.Element, Q.Element, R.Element) 46 | 47 | // Private 48 | private let p: P 49 | private let q: Q 50 | private let r: R 51 | 52 | private var iteratorP: P.AsyncIterator 53 | private var iteratorQ: Q.AsyncIterator 54 | private var iteratorR: R.AsyncIterator 55 | 56 | private var previousElementP: P.Element? 57 | private var previousElementQ: Q.Element? 58 | private var previousElementR: R.Element? 59 | 60 | // MARK: Initialization 61 | 62 | /// Creates an async sequence that only emits elements that don’t match the previous element, 63 | /// as evaluated by a provided closure. 64 | /// - Parameters: 65 | /// - p: An async sequence. 66 | /// - q: An async sequence. 67 | /// - r: An async sequence. 68 | public init( 69 | _ p: P, 70 | _ q: Q, 71 | _ r: R 72 | ) { 73 | self.p = p 74 | self.iteratorP = p.makeAsyncIterator() 75 | 76 | self.q = q 77 | self.iteratorQ = q.makeAsyncIterator() 78 | 79 | self.r = r 80 | self.iteratorR = r.makeAsyncIterator() 81 | } 82 | 83 | // MARK: AsyncSequence 84 | 85 | /// Creates an async iterator that emits elements of this async sequence. 86 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 87 | public func makeAsyncIterator() -> Self { 88 | .init(self.p, self.q, self.r) 89 | } 90 | } 91 | 92 | extension CombineLatest3AsyncSequence: Sendable 93 | where 94 | P: Sendable, 95 | P.Element: Sendable, 96 | P.AsyncIterator: Sendable, 97 | Q: Sendable, 98 | Q.Element: Sendable, 99 | Q.AsyncIterator: Sendable, 100 | R: Sendable, 101 | R.Element: Sendable, 102 | R.AsyncIterator: Sendable {} 103 | 104 | // MARK: AsyncIteratorProtocol 105 | 106 | extension CombineLatest3AsyncSequence: AsyncIteratorProtocol { 107 | /// Produces the next element in the sequence. 108 | /// 109 | /// Continues to call `next()` on it's base iterator and iterator of 110 | /// it's combined sequence. 111 | /// 112 | /// If both iterator's return `nil`, indicating the end of the sequence, this 113 | /// iterator returns `nil`. 114 | /// - Returns: The next element or `nil` if the end of the sequence is reached. 115 | public mutating func next() async rethrows -> Element? { 116 | let elementP = try await self.iteratorP.next() 117 | let elementQ = try await self.iteratorQ.next() 118 | let elementR = try await self.iteratorR.next() 119 | 120 | // All streams have reached their end. 121 | if elementP == nil && elementQ == nil && elementR == nil { 122 | return nil 123 | } 124 | 125 | guard 126 | let unwrappedElementP = elementP ?? self.previousElementP, 127 | let unwrappedElementQ = elementQ ?? self.previousElementQ, 128 | let unwrappedElementR = elementR ?? self.previousElementR else { 129 | // This would happen if one or more streams had no elements to emit but another 130 | // stream had elements. 131 | // 132 | // Combine Latest only emits when it has values from all streams. 133 | self.previousElementP = elementP ?? self.previousElementP 134 | self.previousElementQ = elementQ ?? self.previousElementQ 135 | self.previousElementR = elementR ?? self.previousElementR 136 | return nil 137 | } 138 | 139 | self.previousElementP = unwrappedElementP 140 | self.previousElementQ = unwrappedElementQ 141 | self.previousElementR = unwrappedElementR 142 | 143 | return (unwrappedElementP, unwrappedElementQ, unwrappedElementR) 144 | } 145 | } 146 | 147 | // MARK: Combine latest 148 | 149 | extension AsyncSequence { 150 | /// Combine three async sequences. 151 | /// 152 | /// The combined sequence emits a tuple of the most-recent elements from each sequence 153 | /// when any of them emit a value. 154 | /// 155 | /// If one sequence never emits a value this sequence will finish. 156 | /// 157 | /// ```swift 158 | /// let streamA = .init { continuation in 159 | /// continuation.yield(1) 160 | /// continuation.yield(2) 161 | /// continuation.yield(3) 162 | /// continuation.yield(4) 163 | /// continuation.finish() 164 | /// } 165 | /// 166 | /// let streamB = .init { continuation in 167 | /// continuation.yield(5) 168 | /// continuation.yield(6) 169 | /// continuation.yield(7) 170 | /// continuation.yield(8) 171 | /// continuation.yield(9) 172 | /// continuation.finish() 173 | /// } 174 | /// 175 | /// let streamC = .init { continuation in 176 | /// continuation.yield(10) 177 | /// continuation.yield(11) 178 | /// continuation.finish() 179 | /// } 180 | /// 181 | /// for await value in streamA.combineLatest(streamB, streamC) { 182 | /// print(value) 183 | /// } 184 | /// 185 | /// // Prints: 186 | /// // (1, 5, 10) 187 | /// // (2, 6, 11) 188 | /// // (3, 7, 11) 189 | /// // (4, 8, 11) 190 | /// // (4, 9, 11) 191 | /// ``` 192 | /// - Parameters: 193 | /// - q: Another async sequence to combine with. 194 | /// - r: Another async sequence to combine with. 195 | /// - Returns: A async sequence combines elements from all sequences. 196 | public func combineLatest( 197 | _ q: Q, 198 | _ r: R 199 | ) -> CombineLatest3AsyncSequence where Q: AsyncSequence, R: AsyncSequence { 200 | .init(self, q, r) 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/CombineLatestAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that combines two async sequences. 2 | /// 3 | /// The combined sequence emits a tuple of the most-recent elements from each sequence 4 | /// when any of them emit a value. 5 | /// 6 | /// If one sequence never emits a value this sequence will finish. 7 | /// 8 | /// ```swift 9 | /// let streamA = .init { continuation in 10 | /// continuation.yield(1) 11 | /// continuation.yield(2) 12 | /// continuation.yield(3) 13 | /// continuation.yield(4) 14 | /// continuation.finish() 15 | /// } 16 | /// 17 | /// let streamB = .init { continuation in 18 | /// continuation.yield(5) 19 | /// continuation.yield(6) 20 | /// continuation.yield(7) 21 | /// continuation.yield(8) 22 | /// continuation.yield(9) 23 | /// continuation.finish() 24 | /// } 25 | /// 26 | /// for await value in streamA.combineLatest(streamB) { 27 | /// print(value) 28 | /// } 29 | /// 30 | /// // Prints: 31 | /// // (1, 5) 32 | /// // (2, 6) 33 | /// // (3, 7) 34 | /// // (4, 8) 35 | /// // (4, 9) 36 | /// ``` 37 | public struct CombineLatestAsyncSequence: AsyncSequence { 38 | /// The kind of elements streamed. 39 | public typealias Element = (P.Element, Q.Element) 40 | 41 | // Private 42 | private let p: P 43 | private let q: Q 44 | 45 | private var iteratorP: P.AsyncIterator 46 | private var iteratorQ: Q.AsyncIterator 47 | 48 | private var previousElementP: P.Element? 49 | private var previousElementQ: Q.Element? 50 | 51 | // MARK: Initialization 52 | 53 | /// Creates an async sequence that only emits elements that don’t match the previous element, 54 | /// as evaluated by a provided closure. 55 | /// - Parameters: 56 | /// - p: An async sequence. 57 | /// - q: An async sequence. 58 | public init( 59 | _ p: P, 60 | _ q: Q 61 | ) { 62 | self.p = p 63 | self.iteratorP = p.makeAsyncIterator() 64 | 65 | self.q = q 66 | self.iteratorQ = q.makeAsyncIterator() 67 | } 68 | 69 | // MARK: AsyncSequence 70 | 71 | /// Creates an async iterator that emits elements of this async sequence. 72 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 73 | public func makeAsyncIterator() -> Self { 74 | .init(self.p, self.q) 75 | } 76 | } 77 | 78 | extension CombineLatestAsyncSequence: Sendable 79 | where 80 | P: Sendable, 81 | P.Element: Sendable, 82 | P.AsyncIterator: Sendable, 83 | Q: Sendable, 84 | Q.Element: Sendable, 85 | Q.AsyncIterator: Sendable {} 86 | 87 | // MARK: AsyncIteratorProtocol 88 | 89 | extension CombineLatestAsyncSequence: AsyncIteratorProtocol { 90 | /// Produces the next element in the sequence. 91 | /// 92 | /// Continues to call `next()` on it's base iterator and iterator of 93 | /// it's combined sequence. 94 | /// 95 | /// If both iterator's return `nil`, indicating the end of the sequence, this 96 | /// iterator returns `nil`. 97 | /// - Returns: The next element or `nil` if the end of the sequence is reached. 98 | public mutating func next() async rethrows -> Element? { 99 | let elementP = try await self.iteratorP.next() 100 | let elementQ = try await self.iteratorQ.next() 101 | 102 | // All streams have reached their end. 103 | if elementP == nil && elementQ == nil { 104 | return nil 105 | } 106 | 107 | guard 108 | let unwrappedElementP = elementP ?? self.previousElementP, 109 | let unwrappedElementQ = elementQ ?? self.previousElementQ else { 110 | // This would happen if StreamP had no elements to emit but Stream Q 111 | // had elements. 112 | // 113 | // Combine Latest only emits when it has values from all streams. 114 | self.previousElementP = elementP ?? self.previousElementP 115 | self.previousElementQ = elementQ ?? self.previousElementQ 116 | return nil 117 | } 118 | 119 | self.previousElementP = unwrappedElementP 120 | self.previousElementQ = unwrappedElementQ 121 | 122 | return (unwrappedElementP, unwrappedElementQ) 123 | } 124 | } 125 | 126 | // MARK: Combine latest 127 | 128 | extension AsyncSequence { 129 | /// Combine with an additional async sequence to produce a `AsyncCombineLatest2Sequence`. 130 | /// 131 | /// The combined sequence emits a tuple of the most-recent elements from each sequence 132 | /// when any of them emit a value. 133 | /// 134 | /// ```swift 135 | /// let streamA = .init { continuation in 136 | /// continuation.yield(1) 137 | /// continuation.yield(2) 138 | /// continuation.yield(3) 139 | /// continuation.yield(4) 140 | /// continuation.finish() 141 | /// } 142 | /// 143 | /// let streamB = .init { continuation in 144 | /// continuation.yield(5) 145 | /// continuation.yield(6) 146 | /// continuation.yield(7) 147 | /// continuation.yield(8) 148 | /// continuation.yield(9) 149 | /// continuation.finish() 150 | /// } 151 | /// 152 | /// for await value in self.streamA.combineLatest(self.streamB) { 153 | /// print(value) 154 | /// } 155 | /// 156 | /// // Prints: 157 | /// // (1, 5) 158 | /// // (2, 6) 159 | /// // (3, 7) 160 | /// // (4, 8) 161 | /// // (4, 9) 162 | /// ``` 163 | /// - Parameters: 164 | /// - other: Another async sequence to combine with. 165 | /// - Returns: A async sequence combines elements from this and another async sequence. 166 | public func combineLatest( 167 | _ other: Q 168 | ) -> CombineLatestAsyncSequence where Q: AsyncSequence { 169 | .init(self, other) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/CurrentElementAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// A async sequence that wraps a single value and emits a new element whenever the element changes. 2 | /// 3 | /// ```swift 4 | /// let sequence = CurrentElementAsyncSequence(0) 5 | /// print(await sequence.element) 6 | /// 7 | /// await stream.yield(1) 8 | /// print(await sequence.element) 9 | /// 10 | /// await stream.yield(2) 11 | /// await stream.yield(3) 12 | /// await stream.yield(4) 13 | /// print(await sequence.element) 14 | /// 15 | /// // Prints: 16 | /// // 0 17 | /// // 1 18 | /// // 4 19 | /// ``` 20 | public actor CurrentElementAsyncSequence: AsyncSequence where Element: Sendable { 21 | /// The element wrapped by this async sequence, emitted as a new element whenever it changes. 22 | public private(set) var element: Element 23 | 24 | // Private 25 | private let stream: _Stream 26 | 27 | // MARK: Initialization 28 | 29 | /// Creates an async sequence that emits elements only after a specified time interval elapses between emissions. 30 | /// - Parameters: 31 | /// - element: The async sequence in which this sequence receives it's elements. 32 | public init(_ element: Element) { 33 | self.stream = .init(element) 34 | self.element = element 35 | } 36 | 37 | // MARK: AsyncSequence 38 | 39 | /// Creates an async iterator that emits elements of this async sequence. 40 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 41 | nonisolated public func makeAsyncIterator() -> AsyncStream.Iterator { 42 | self.stream.makeAsyncIterator() 43 | } 44 | 45 | // MARK: API 46 | 47 | /// Yield a new element to the sequence. 48 | /// 49 | /// Yielding a new element will update this async sequence's `element` property 50 | /// along with emitting it through the sequence. 51 | /// - Parameter element: The element to yield. 52 | public func yield(_ element: Element) { 53 | self.stream.yield(element) 54 | self.element = element 55 | } 56 | 57 | /// Mark the sequence as finished by having it's iterator emit nil. 58 | /// 59 | /// Once finished, any calls to yield will result in no change. 60 | public func finish() { 61 | self.stream.finish() 62 | } 63 | 64 | /// Emit one last element beford marking the sequence as finished by having it's iterator emit nil. 65 | /// 66 | /// Once finished, any calls to yield will result in no change. 67 | /// - Parameter element: The element to emit. 68 | public func finish(with element: Element) { 69 | self.stream.finish(with: element) 70 | self.element = element 71 | } 72 | } 73 | 74 | // MARK: Stream 75 | 76 | fileprivate struct _Stream: AsyncSequence { 77 | private var stream: AsyncStream! 78 | private var continuation: AsyncStream.Continuation! 79 | 80 | // MARK: Intialization 81 | 82 | fileprivate init(_ element: Element) { 83 | self.stream = .init { self.continuation = $0 } 84 | self.yield(element) 85 | } 86 | 87 | fileprivate func makeAsyncIterator() -> AsyncStream.Iterator { 88 | self.stream.makeAsyncIterator() 89 | } 90 | 91 | // MARK: API 92 | 93 | fileprivate func yield(_ element: Element) { 94 | self.continuation.yield(element) 95 | } 96 | 97 | fileprivate func finish() { 98 | self.continuation.finish() 99 | } 100 | 101 | fileprivate func finish(with element: Element) { 102 | self.continuation.finish(with: element) 103 | } 104 | } 105 | 106 | extension _Stream: Sendable where Element: Sendable {} 107 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/DebounceAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A async sequence that emits elements only after a specified time interval elapses between emissions. 4 | /// 5 | /// Use `DebounceAsyncSequence` async sequence to control the number of values and time between 6 | /// delivery of values from the base async sequence. This async sequence is useful to process bursty 7 | /// or high-volume async sequences where you need to reduce the number of elements emitted to a rate you specify. 8 | /// 9 | /// HT to [swift-async-algorithms](https://github.com/apple/swift-async-algorithms) for helping 10 | /// realise my woes of rethrows. 11 | /// 12 | /// ```swift 13 | /// let sequence = AsyncStream { continuation in 14 | /// continuation.yield(0) 15 | /// try? await Task.sleep(seconds: 0.1) 16 | /// continuation.yield(1) 17 | /// try? await Task.sleep(seconds: 0.1) 18 | /// continuation.yield(2) 19 | /// continuation.yield(3) 20 | /// continuation.yield(4) 21 | /// continuation.yield(5) 22 | /// try? await Task.sleep(seconds: 0.1) 23 | /// continuation.finish() 24 | /// } 25 | /// 26 | /// for element in try await sequence.debounce(for: 0.1) { 27 | /// print(element) 28 | /// } 29 | /// 30 | /// // Prints: 31 | /// // 0 32 | /// // 1 33 | /// // 5 34 | /// ``` 35 | public struct DebounceAsyncSequence: AsyncSequence 36 | where 37 | T.AsyncIterator: Sendable, 38 | T.Element: Sendable { 39 | /// The kind of elements streamed. 40 | public typealias Element = T.Element 41 | 42 | // Private 43 | private var base: T 44 | private var dueTime: TimeInterval 45 | 46 | // MARK: Initialization 47 | 48 | /// Creates an async sequence that emits elements only after a specified time interval elapses between emissions. 49 | /// - Parameters: 50 | /// - base: The async sequence in which this sequence receives it's elements. 51 | /// - dueTime: The amount of time the async sequence should wait before emitting an element. 52 | public init( 53 | _ base: T, 54 | dueTime: TimeInterval 55 | ) { 56 | self.base = base 57 | self.dueTime = dueTime 58 | } 59 | 60 | // MARK: AsyncSequence 61 | 62 | /// Creates an async iterator that emits elements of this async sequence. 63 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 64 | public func makeAsyncIterator() -> Iterator { 65 | Iterator(base: self.base.makeAsyncIterator(), dueTime: self.dueTime) 66 | } 67 | } 68 | 69 | extension DebounceAsyncSequence: Sendable 70 | where 71 | T: Sendable {} 72 | 73 | // MARK: Iterator 74 | 75 | extension DebounceAsyncSequence { 76 | public struct Iterator: AsyncIteratorProtocol { 77 | private var base: T.AsyncIterator 78 | private var dueTime: TimeInterval 79 | private var resultTask: Task? 80 | 81 | // MARK: Initialization 82 | 83 | init( 84 | base: T.AsyncIterator, 85 | dueTime: TimeInterval 86 | ) { 87 | self.base = base 88 | self.dueTime = dueTime 89 | } 90 | 91 | // MARK: AsyncIteratorProtocol 92 | 93 | public mutating func next() async rethrows -> Element? { 94 | var lastResult: Result? 95 | var lastEmission: Date = .init() 96 | 97 | while true { 98 | let resultTask = self.resultTask ?? Task { [base] in 99 | var iterator = base 100 | do { 101 | let value = try await iterator.next() 102 | return .winner(.success(value), iterator: iterator) 103 | } catch { 104 | return .winner(.failure(error), iterator: iterator) 105 | } 106 | } 107 | self.resultTask = nil 108 | 109 | lastEmission = Date() 110 | let delay = UInt64(self.dueTime - Date().timeIntervalSince(lastEmission)) * 1_000_000_000 111 | let sleep = Task { 112 | try? await Task.sleep(nanoseconds: delay) 113 | return .sleep 114 | } 115 | 116 | let tasks = [resultTask, sleep] 117 | let firstTask = await { () async -> Task in 118 | let raceCoordinator = TaskRaceCoodinator() 119 | return await withTaskCancellationHandler( 120 | operation: { 121 | await withCheckedContinuation { continuation in 122 | for task in tasks { 123 | Task { 124 | _ = await task.result 125 | if await raceCoordinator.isFirstToCrossLine(task) { 126 | continuation.resume(returning: task) 127 | } 128 | } 129 | } 130 | } 131 | }, 132 | onCancel: { 133 | for task in tasks { 134 | task.cancel() 135 | } 136 | } 137 | ) 138 | }() 139 | 140 | switch await firstTask.value { 141 | case .winner(let result, let iterator): 142 | lastResult = result 143 | lastEmission = Date() 144 | self.base = iterator 145 | 146 | switch result { 147 | case .success(let value): 148 | // Base sequence has reached it's end. 149 | if value == nil { 150 | return nil 151 | } 152 | case .failure: 153 | try result._rethrowError() 154 | } 155 | case .sleep: 156 | self.resultTask = resultTask 157 | if let result = lastResult { 158 | return try result._rethrowGet() 159 | } 160 | } 161 | } 162 | } 163 | } 164 | } 165 | 166 | extension DebounceAsyncSequence.Iterator: Sendable 167 | where 168 | T.AsyncIterator: Sendable, 169 | T.Element: Sendable {} 170 | 171 | // MARK: Race result 172 | 173 | extension DebounceAsyncSequence.Iterator { 174 | fileprivate enum RaceResult { 175 | case winner(Result, iterator: T.AsyncIterator) 176 | case sleep 177 | } 178 | } 179 | 180 | // MARK: Task race coordinator 181 | 182 | fileprivate actor TaskRaceCoodinator where Success: Sendable { 183 | private var winner: Task? 184 | 185 | func isFirstToCrossLine(_ task: Task) -> Bool { 186 | guard self.winner == nil else { return false } 187 | self.winner = task 188 | return true 189 | } 190 | } 191 | 192 | // MARK: Debounce 193 | 194 | extension AsyncSequence where AsyncIterator: Sendable, Element: Sendable { 195 | /// Emits elements only after a specified time interval elapses between emissions. 196 | /// 197 | /// Use the `debounce` operator to control the number of values and time between 198 | /// delivery of values from the base async sequence. This operator is useful to process bursty 199 | /// or high-volume async sequences where you need to reduce the number of elements emitted to a rate you specify. 200 | /// 201 | /// ```swift 202 | /// let sequence = AsyncStream { continuation in 203 | /// continuation.yield(0) 204 | /// try? await Task.sleep(seconds: 0.1) 205 | /// continuation.yield(1) 206 | /// try? await Task.sleep(seconds: 0.1) 207 | /// continuation.yield(2) 208 | /// continuation.yield(3) 209 | /// continuation.yield(4) 210 | /// continuation.yield(5) 211 | /// try? await Task.sleep(seconds: 0.1) 212 | /// continuation.finish() 213 | /// } 214 | /// 215 | /// for element in try await sequence.debounce(for: 0.1) { 216 | /// print(element) 217 | /// } 218 | /// 219 | /// // Prints: 220 | /// // 0 221 | /// // 1 222 | /// // 5 223 | /// ``` 224 | /// - Parameters: 225 | /// - base: The async sequence in which this sequence receives it's elements. 226 | /// - dueTime: The amount of time the async sequence should wait before emitting an element. 227 | /// - Returns: A `DebounceAsyncSequence` instance. 228 | public func debounce(for dueTime: TimeInterval) -> DebounceAsyncSequence { 229 | .init(self, dueTime: dueTime) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/DelayAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// Delays emission of all elements by the provided interval. 4 | /// 5 | /// ```swift 6 | /// let stream = AsyncStream { continuation in 7 | /// continuation.yield(0) 8 | /// continuation.yield(1) 9 | /// continuation.yield(2) 10 | /// continuation.finish() 11 | /// } 12 | /// 13 | /// let start = Date.now 14 | /// for element in try await self.stream.delay(for: 0.5) { 15 | /// print("\(element) - \(Date.now.timeIntervalSince(start))") 16 | /// } 17 | /// 18 | /// // Prints: 19 | /// // 0 - 0.5 20 | /// // 1 - 1.0 21 | /// // 2 - 1.5 22 | /// ``` 23 | public struct DelayAsyncSequence: AsyncSequence { 24 | /// The kind of elements streamed. 25 | public typealias Element = T.Element 26 | 27 | // Private 28 | private let base: T 29 | private let interval: TimeInterval 30 | 31 | // MARK: Initialization 32 | 33 | /// Creates an async sequence that delays emission of elements and completion. 34 | /// - Parameters: 35 | /// - base: The async sequence in which this sequence receives it's elements. 36 | /// - interval: The amount of time the async sequence should wait before emitting an element. 37 | public init( 38 | _ base: T, 39 | interval: TimeInterval 40 | ) { 41 | self.base = base 42 | self.interval = interval 43 | } 44 | 45 | // MARK: AsyncSequence 46 | 47 | /// Creates an async iterator that emits elements of this async sequence. 48 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 49 | public func makeAsyncIterator() -> Iterator { 50 | Iterator(interval: self.interval, iterator: self.base.makeAsyncIterator()) 51 | } 52 | } 53 | 54 | extension DelayAsyncSequence: Sendable 55 | where 56 | T: Sendable {} 57 | 58 | // MARK: Iterator 59 | 60 | extension DelayAsyncSequence { 61 | public struct Iterator: AsyncIteratorProtocol { 62 | private let interval: TimeInterval 63 | private var iterator: T.AsyncIterator 64 | private var lastEmission: Date? 65 | 66 | init(interval: TimeInterval, iterator: T.AsyncIterator) { 67 | self.interval = interval 68 | self.iterator = iterator 69 | } 70 | 71 | public mutating func next() async rethrows -> Element? { 72 | defer { self.lastEmission = Date() } 73 | 74 | let lastEmission = self.lastEmission ?? Date() 75 | let delay = self.interval - Date().timeIntervalSince(lastEmission) 76 | if delay > 0 { 77 | try? await Task.sleep(seconds: delay) 78 | } 79 | 80 | return try await self.iterator.next() 81 | } 82 | } 83 | } 84 | 85 | extension DelayAsyncSequence.Iterator: Sendable 86 | where 87 | T.AsyncIterator: Sendable {} 88 | 89 | // MARK: Delay 90 | 91 | extension AsyncSequence { 92 | /// Delays emission of all elements by the provided interval. 93 | /// 94 | /// ```swift 95 | /// let stream = AsyncStream { continuation in 96 | /// continuation.yield(0) 97 | /// continuation.yield(1) 98 | /// continuation.yield(2) 99 | /// continuation.finish() 100 | /// } 101 | /// 102 | /// let start = Date.now 103 | /// for element in try await self.stream.delay(for: 0.5) { 104 | /// print("\(element) - \(Date.now.timeIntervalSince(start))") 105 | /// } 106 | /// 107 | /// // Prints: 108 | /// // 0 - 0.5 109 | /// // 1 - 1.0 110 | /// // 2 - 1.5 111 | /// ``` 112 | /// - Parameters: 113 | /// - base: The async sequence in which this sequence receives it's elements. 114 | /// - interval: The amount of time the async sequence should wait before emitting an element. 115 | /// - Returns: A `DebounceAsyncSequence` instance. 116 | public func delay(for interval: TimeInterval) -> DelayAsyncSequence { 117 | .init(self, interval: interval) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/Empty.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that only emits the provided value once. 2 | /// 3 | /// ```swift 4 | /// Empty().sink( 5 | /// receiveValue: { print($0) }, 6 | /// receiveCompletion: { completion in 7 | /// switch completion { 8 | /// case .finished: 9 | /// print("Finished") 10 | /// case .failure: 11 | /// print("Failed") 12 | /// } 13 | /// } 14 | /// ) 15 | /// 16 | /// // Prints: 17 | /// // Finished 18 | /// ``` 19 | public struct Empty: AsyncSequence, Sendable { 20 | private let completeImmediately: Bool 21 | 22 | // MARK: Initialization 23 | 24 | /// Creates an empty async sequence. 25 | /// 26 | /// - Parameter completeImmediately: A Boolean value that indicates whether 27 | /// the async sequence should immediately finish. 28 | public init(completeImmediately: Bool = true) { 29 | self.completeImmediately = completeImmediately 30 | } 31 | 32 | // MARK: AsyncSequence 33 | 34 | /// Creates an async iterator that emits elements of this async sequence. 35 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 36 | public func makeAsyncIterator() -> Self { 37 | .init(completeImmediately: self.completeImmediately) 38 | } 39 | } 40 | 41 | // MARK: AsyncIteratorProtocol 42 | 43 | extension Empty: AsyncIteratorProtocol { 44 | /// Produces the next element in the sequence. 45 | /// 46 | /// Because this is an empty sequence, this will always be nil. 47 | /// 48 | /// - Returns: `nil` as this is an empty sequence. 49 | public mutating func next() async -> Element? { 50 | if !self.completeImmediately { 51 | try? await Task.sleep(seconds: 999_999_999) 52 | } 53 | 54 | return nil 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/Fail.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that immediately throws an error when iterated. 2 | /// 3 | /// Once the error has been thrown, the iterator will return nil to mark the end of the sequence. 4 | /// 5 | /// ```swift 6 | /// let stream = Fail(error: TestError()) 7 | /// 8 | /// do { 9 | /// for try await value in stream { 10 | /// print(value) 11 | /// } 12 | /// } catch { 13 | /// print("Error!") 14 | /// } 15 | /// 16 | /// // Prints: 17 | /// // Error! 18 | /// ``` 19 | public struct Fail: AsyncSequence, Sendable where Failure: Error { 20 | private let error: Failure 21 | private var hasThownError = false 22 | 23 | // MARK: Initialization 24 | 25 | /// Creates an async sequence that throws an error. 26 | /// - Parameters: 27 | /// - error: The error to throw. 28 | public init(error: Failure) { 29 | self.error = error 30 | } 31 | 32 | // MARK: AsyncSequence 33 | 34 | /// Creates an async iterator that emits elements of this async sequence. 35 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 36 | public func makeAsyncIterator() -> Self { 37 | .init(error: self.error) 38 | } 39 | } 40 | 41 | // MARK: AsyncIteratorProtocol 42 | 43 | extension Fail: AsyncIteratorProtocol { 44 | /// Produces the next element in the sequence. 45 | /// - Returns: The next element or `nil` if the end of the sequence is reached. 46 | public mutating func next() async throws -> Element? { 47 | defer { self.hasThownError = true } 48 | guard !self.hasThownError else { return nil } 49 | 50 | throw self.error 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/Just.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that only emits the provided value once. 2 | /// 3 | /// ```swift 4 | /// let stream = Just(1) 5 | /// 6 | /// for await value in stream { 7 | /// print(value) 8 | /// } 9 | /// 10 | /// // Prints: 11 | /// // 1 12 | /// ``` 13 | public struct Just: AsyncSequence { 14 | private let element: Element 15 | private var emittedElement = false 16 | 17 | // MARK: Initialization 18 | 19 | /// Creates an async sequence that emits an element once. 20 | /// - Parameters: 21 | /// - element: The element to emit. 22 | public init(_ element: Element) { 23 | self.element = element 24 | } 25 | 26 | // MARK: AsyncSequence 27 | 28 | /// Creates an async iterator that emits elements of this async sequence. 29 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 30 | public func makeAsyncIterator() -> Self { 31 | .init(self.element) 32 | } 33 | } 34 | 35 | extension Just: Sendable where Element: Sendable {} 36 | 37 | // MARK: AsyncIteratorProtocol 38 | 39 | extension Just: AsyncIteratorProtocol { 40 | /// Produces the next element in the sequence. 41 | /// - Returns: The next element or `nil` if the end of the sequence is reached. 42 | public mutating func next() async -> Element? { 43 | guard !self.emittedElement else { return nil } 44 | defer { self.emittedElement = true } 45 | 46 | return self.element 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/Merge3AsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that merges three async sequences. 2 | /// 3 | /// The sequences are iterated through in parallel. 4 | /// 5 | /// ```swift 6 | /// let streamA = .init { continuation in 7 | /// continuation.yield(1) 8 | /// continuation.yield(4) 9 | /// continuation.finish() 10 | /// } 11 | /// 12 | /// let streamB = .init { continuation in 13 | /// continuation.yield(2) 14 | /// continuation.finish() 15 | /// } 16 | /// 17 | /// let streamC = .init { continuation in 18 | /// continuation.yield(3) 19 | /// continuation.finish() 20 | /// } 21 | /// 22 | /// for await value in self.streamA.merge(with: self.streamB, self.streamC) { 23 | /// print(value) 24 | /// } 25 | /// 26 | /// // Prints: 27 | /// // 1 28 | /// // 2 29 | /// // 3 30 | /// // 4 31 | /// ``` 32 | public struct Merge3AsyncSequence: AsyncSequence, Sendable where T: Sendable { 33 | public typealias Element = T.Element 34 | 35 | // Private 36 | private let p: T 37 | private let q: T 38 | private let r: T 39 | 40 | // MARK: Initialization 41 | 42 | /// Creates an async sequence that merges the provided async sequence's. 43 | /// - Parameters: 44 | /// - p: An async sequence. 45 | /// - q: An async sequence. 46 | /// - r: An async sequence. 47 | public init( 48 | _ p: T, 49 | _ q: T, 50 | _ r: T 51 | ) { 52 | self.p = p 53 | self.q = q 54 | self.r = r 55 | } 56 | 57 | // MARK: AsyncSequence 58 | 59 | /// Creates an async iterator that emits elements of this async sequence. 60 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 61 | public func makeAsyncIterator() -> Iterator { 62 | Iterator(self.p, self.q, self.r) 63 | } 64 | } 65 | 66 | // MARK: Iterator 67 | 68 | extension Merge3AsyncSequence { 69 | public struct Iterator: AsyncIteratorProtocol { 70 | private var _iterator: AsyncThrowingStream.Iterator! 71 | 72 | // MARK: Initialization 73 | 74 | init( 75 | _ p: T, 76 | _ q: T, 77 | _ r: T 78 | ) { 79 | let stream = self.merge(p, q, r) 80 | self._iterator = stream.makeAsyncIterator() 81 | } 82 | 83 | // MARK: Merge 84 | 85 | private func merge( 86 | _ p: T, 87 | _ q: T, 88 | _ r: T 89 | ) -> AsyncThrowingStream { 90 | .init { continuation in 91 | let handler: @Sendable ( 92 | _ sequence: T, 93 | _ continuation: AsyncThrowingStream.Continuation 94 | ) async throws -> Void = { sequence, continuation in 95 | for try await event in sequence { 96 | continuation.yield(event) 97 | } 98 | } 99 | 100 | async let resultP: () = handler(p, continuation) 101 | async let resultQ: () = handler(q, continuation) 102 | async let resultR: () = handler(r, continuation) 103 | 104 | do { 105 | _ = try await [resultP, resultQ, resultR] 106 | continuation.finish() 107 | } catch { 108 | continuation.finish(throwing: error) 109 | } 110 | } 111 | } 112 | 113 | // MARK: AsyncIteratorProtocol 114 | 115 | public mutating func next() async rethrows -> Element? { 116 | var result: Result 117 | do { 118 | result = .success(try await self._iterator.next()) 119 | } catch { 120 | result = .failure(error) 121 | } 122 | 123 | switch result { 124 | case .success(let element): 125 | return element 126 | case .failure: 127 | try result._rethrowError() 128 | } 129 | } 130 | } 131 | } 132 | 133 | // MARK: Merge 134 | 135 | extension AsyncSequence { 136 | /// An asynchronous sequence that merges three async sequences. 137 | /// 138 | /// The sequences are iterated through in parallel. 139 | /// 140 | /// ```swift 141 | /// let streamA = .init { continuation in 142 | /// continuation.yield(1) 143 | /// continuation.yield(4) 144 | /// continuation.finish() 145 | /// } 146 | /// 147 | /// let streamB = .init { continuation in 148 | /// continuation.yield(2) 149 | /// continuation.finish() 150 | /// } 151 | /// 152 | /// let streamC = .init { continuation in 153 | /// continuation.yield(3) 154 | /// continuation.finish() 155 | /// } 156 | /// 157 | /// for await value in streamA.merge(with: streamB, streamC) { 158 | /// print(value) 159 | /// } 160 | /// 161 | /// // Prints: 162 | /// // 1 163 | /// // 2 164 | /// // 3 165 | /// // 4 166 | /// ``` 167 | /// - Parameters: 168 | /// - q: An async sequence. 169 | /// - r: An async sequence. 170 | /// - Returns: A async sequence merges elements from this and another async sequence. 171 | public func merge( 172 | with q: Self, 173 | _ r: Self 174 | ) -> Merge3AsyncSequence where Self: Sendable { 175 | .init(self, q, r) 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/MergeAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that merges two async sequences. 2 | /// 3 | /// The sequences are iterated through in parallel. 4 | /// 5 | /// ```swift 6 | /// let streamA = .init { continuation in 7 | /// continuation.yield(1) 8 | /// continuation.yield(2) 9 | /// continuation.yield(3) 10 | /// continuation.yield(4) 11 | /// continuation.finish() 12 | /// } 13 | /// 14 | /// let streamB = .init { continuation in 15 | /// continuation.yield(5) 16 | /// continuation.yield(6) 17 | /// continuation.yield(7) 18 | /// continuation.yield(8) 19 | /// continuation.yield(9) 20 | /// continuation.finish() 21 | /// } 22 | /// 23 | /// for await value in streamA.merge(with: streamB) { 24 | /// print(value) 25 | /// } 26 | /// 27 | /// // Prints: 28 | /// // 1 29 | /// // 5 30 | /// // 2 31 | /// // 6 32 | /// // 3 33 | /// // 7 34 | /// // 4 35 | /// // 8 36 | /// // 9 37 | /// ``` 38 | public struct MergeAsyncSequence: AsyncSequence, Sendable where T: AsyncSequence, T: Sendable { 39 | public typealias Element = T.Element 40 | 41 | // Private 42 | private let p: T 43 | private let q: T 44 | 45 | // MARK: Initialization 46 | 47 | /// Creates an async sequence that merges the provided async sequence's. 48 | /// - Parameters: 49 | /// - p: An async sequence. 50 | /// - q: An async sequence. 51 | public init( 52 | _ p: T, 53 | _ q: T 54 | ) { 55 | self.p = p 56 | self.q = q 57 | } 58 | 59 | // MARK: AsyncSequence 60 | 61 | /// Creates an async iterator that emits elements of this async sequence. 62 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 63 | public func makeAsyncIterator() -> Iterator { 64 | Iterator(self.p, self.q) 65 | } 66 | } 67 | 68 | // MARK: Iterator 69 | 70 | extension MergeAsyncSequence { 71 | public struct Iterator: AsyncIteratorProtocol { 72 | private var _iterator: AsyncThrowingStream.Iterator! 73 | 74 | // MARK: Initialization 75 | 76 | init( 77 | _ p: T, 78 | _ q: T 79 | ) { 80 | let stream = self.merge(p, q) 81 | self._iterator = stream.makeAsyncIterator() 82 | } 83 | 84 | // MARK: Merge 85 | 86 | private func merge( 87 | _ p: T, 88 | _ q: T 89 | ) -> AsyncThrowingStream { 90 | .init { continuation in 91 | let handler: @Sendable ( 92 | _ sequence: T, 93 | _ continuation: AsyncThrowingStream.Continuation 94 | ) async throws -> Void = { sequence, continuation in 95 | for try await event in sequence { 96 | continuation.yield(event) 97 | } 98 | } 99 | 100 | async let resultA: () = handler(p, continuation) 101 | async let resultB: () = handler(q, continuation) 102 | 103 | do { 104 | _ = try await [resultA, resultB] 105 | continuation.finish() 106 | } catch { 107 | continuation.finish(throwing: error) 108 | } 109 | } 110 | } 111 | 112 | // MARK: AsyncIteratorProtocol 113 | 114 | public mutating func next() async rethrows -> Element? { 115 | var result: Result 116 | do { 117 | result = .success(try await self._iterator.next()) 118 | } catch { 119 | result = .failure(error) 120 | } 121 | 122 | switch result { 123 | case .success(let element): 124 | return element 125 | case .failure: 126 | try result._rethrowError() 127 | } 128 | } 129 | } 130 | } 131 | 132 | // MARK: Merge 133 | 134 | extension AsyncSequence { 135 | /// An asynchronous sequence that merges two async sequence. 136 | /// 137 | /// The sequences are iterated through in parallel. 138 | /// 139 | /// ```swift 140 | /// let streamA = .init { continuation in 141 | /// continuation.yield(1) 142 | /// continuation.yield(2) 143 | /// continuation.yield(3) 144 | /// continuation.yield(4) 145 | /// continuation.finish() 146 | /// } 147 | /// 148 | /// let streamB = .init { continuation in 149 | /// continuation.yield(5) 150 | /// continuation.yield(6) 151 | /// continuation.yield(7) 152 | /// continuation.yield(8) 153 | /// continuation.yield(9) 154 | /// continuation.finish() 155 | /// } 156 | /// 157 | /// for await value in streamA.merge(with: streamB) { 158 | /// print(value) 159 | /// } 160 | /// 161 | /// // Prints: 162 | /// // 1 163 | /// // 5 164 | /// // 2 165 | /// // 6 166 | /// // 3 167 | /// // 7 168 | /// // 4 169 | /// // 8 170 | /// // 9 171 | /// ``` 172 | /// - Parameters: 173 | /// - other: Another async sequence to merge with. 174 | /// - Returns: A async sequence merges elements from this and another async sequence. 175 | public func merge( 176 | with other: Self 177 | ) -> MergeAsyncSequence where Self: Sendable { 178 | .init(self, other) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/NotificationCenterAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An async sequence that emits when a notification center broadcasts notifications. 4 | /// 5 | /// ```swift 6 | /// let sequence = NotificationCenterAsyncSequence( 7 | /// notificationCenter: .default, 8 | /// notificationName: UIDevice.orientationDidChangeNotification, 9 | /// object: nil 10 | /// ) 11 | /// 12 | /// for await element in sequence { 13 | /// print(element) 14 | /// } 15 | /// 16 | /// ``` 17 | public struct NotificationCenterAsyncSequence: AsyncSequence { 18 | /// The kind of elements streamed. 19 | public typealias Element = Notification 20 | 21 | // Private 22 | private var notificationCenter: NotificationCenter 23 | private var notificationName: Notification.Name 24 | private var object: AnyObject? 25 | 26 | // MARK: Initialization 27 | 28 | public init( 29 | notificationCenter: NotificationCenter, 30 | notificationName: Notification.Name, 31 | object: AnyObject? = nil 32 | ) { 33 | self.notificationCenter = notificationCenter 34 | self.notificationName = notificationName 35 | self.object = object 36 | } 37 | 38 | // MARK: AsyncSequence 39 | 40 | /// Creates an async iterator that emits elements of this async sequence. 41 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 42 | public func makeAsyncIterator() -> Iterator { 43 | .init( 44 | notificationCenter: self.notificationCenter, 45 | notificationName: self.notificationName, 46 | object: self.object 47 | ) 48 | } 49 | } 50 | 51 | // MARK: Iterator 52 | 53 | extension NotificationCenterAsyncSequence { 54 | public struct Iterator: AsyncIteratorProtocol { 55 | private var notificationCenter: NotificationCenter 56 | private var passthroughAsyncSequence: PassthroughAsyncSequence = .init() 57 | private var iterator: PassthroughAsyncSequence.AsyncIterator 58 | private var observer: Any? 59 | 60 | // MARK: Initialization 61 | 62 | init( 63 | notificationCenter: NotificationCenter, 64 | notificationName: Notification.Name, 65 | object: AnyObject? = nil 66 | ) { 67 | self.notificationCenter = notificationCenter 68 | self.iterator = self.passthroughAsyncSequence.makeAsyncIterator() 69 | 70 | self.observer = self.notificationCenter.addObserver( 71 | forName: notificationName, 72 | object: object, 73 | queue: nil 74 | ) { [passthroughAsyncSequence] notification in 75 | passthroughAsyncSequence.yield(notification) 76 | } 77 | } 78 | 79 | // MARK: AsyncIteratorProtocol 80 | 81 | public mutating func next() async -> Element? { 82 | guard let value = await self.iterator.next() else { 83 | if let observer = observer { 84 | self.notificationCenter.removeObserver(observer) 85 | } 86 | return nil 87 | } 88 | 89 | return value 90 | } 91 | } 92 | } 93 | 94 | // MARK: Notification center 95 | 96 | extension NotificationCenter { 97 | /// Returns an async sequence that emits when the notification 98 | /// center broadcasts notifications. 99 | /// 100 | /// ```swift 101 | /// let sequence = NotificationCenter.default.sequence(for: UIDevice.orientationDidChangeNotification) 102 | /// 103 | /// for await element in sequence { 104 | /// print(element) 105 | /// } 106 | /// 107 | /// ``` 108 | public func sequence( 109 | for name: Notification.Name, 110 | object: AnyObject? = nil 111 | ) -> NotificationCenterAsyncSequence { 112 | .init( 113 | notificationCenter: self, 114 | notificationName: name, 115 | object: object 116 | ) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/PassthroughAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// A async sequence that broadcasts elements. 2 | /// 3 | /// ```swift 4 | /// let sequence = PassthroughAsyncSequence() 5 | /// sequence.yield(0) 6 | /// sequence.yield(1) 7 | /// sequence.yield(2) 8 | /// sequence.finish() 9 | /// 10 | /// for await value in sequence { 11 | /// print(value) 12 | /// } 13 | /// 14 | /// // Prints: 15 | /// // 0 16 | /// // 1 17 | /// // 2 18 | /// ``` 19 | public struct PassthroughAsyncSequence: AsyncSequence { 20 | private var stream: AsyncStream! 21 | private var continuation: AsyncStream.Continuation! 22 | 23 | // MARK: Initialization 24 | 25 | /// Creates an async sequence that broadcasts elements. 26 | public init() { 27 | self.stream = .init { self.continuation = $0 } 28 | } 29 | 30 | // MARK: AsyncSequence 31 | 32 | /// Creates an async iterator that emits elements of this async sequence. 33 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 34 | public func makeAsyncIterator() -> AsyncStream.Iterator { 35 | self.stream.makeAsyncIterator() 36 | } 37 | 38 | // MARK: API 39 | 40 | /// Yield a new element to the sequence. 41 | /// 42 | /// Yielding a new element will emit it through the sequence. 43 | /// - Parameter element: The element to yield. 44 | public func yield(_ element: Element) { 45 | self.continuation.yield(element) 46 | } 47 | 48 | /// Mark the sequence as finished by having it's iterator emit nil. 49 | /// 50 | /// Once finished, any calls to yield will result in no change. 51 | public func finish() { 52 | self.continuation.finish() 53 | } 54 | 55 | /// Emit one last element beford marking the sequence as finished by having it's iterator emit nil. 56 | /// 57 | /// Once finished, any calls to yield will result in no change. 58 | /// - Parameter element: The element to emit. 59 | public func finish(with element: Element) { 60 | self.continuation.finish(with: element) 61 | } 62 | } 63 | 64 | extension PassthroughAsyncSequence: Sendable where Element: Sendable {} 65 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/RemoveDuplicatesAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that streams only elements from the base asynchronous sequence 2 | /// that don’t match the previous element. 3 | /// 4 | /// ```swift 5 | /// let stream = .init { continuation in 6 | /// continuation.yield(1) 7 | /// continuation.yield(1) 8 | /// continuation.yield(2) 9 | /// continuation.yield(3) 10 | /// continuation.finish() 11 | /// } 12 | /// 13 | /// for await value in stream.removeDuplicates() { 14 | /// print(value) 15 | /// } 16 | /// 17 | /// // Prints: 18 | /// // 1 19 | /// // 2 20 | /// // 3 21 | /// ``` 22 | public struct RemoveDuplicatesAsyncSequence: AsyncSequence where Base.Element: Equatable { 23 | /// The kind of elements streamed. 24 | public typealias Element = Base.Element 25 | 26 | // A predicate closure for evaluating whether two elements are duplicates. 27 | public typealias Predicate = @Sendable (_ previous: Base.Element, _ current: Base.Element) -> Bool 28 | 29 | // Private 30 | private let base: Base 31 | private let predicate: Predicate 32 | 33 | // MARK: Initialization 34 | 35 | /// Creates an async that only emits elements that don’t match the previous element, 36 | /// as evaluated by a provided closure. 37 | /// - Parameters: 38 | /// - base: The async sequence in which this sequence receives it's elements. 39 | /// - predicate: A closure to evaluate whether two elements are equivalent. 40 | public init( 41 | base: Base, 42 | predicate: @escaping Predicate 43 | ) { 44 | self.base = base 45 | self.predicate = predicate 46 | } 47 | 48 | // MARK: AsyncSequence 49 | 50 | /// Creates an async iterator that emits elements of this async sequence. 51 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 52 | public func makeAsyncIterator() -> Iterator { 53 | Iterator(base: self.base, predicate: self.predicate) 54 | } 55 | } 56 | 57 | extension RemoveDuplicatesAsyncSequence: Sendable 58 | where 59 | Base: Sendable {} 60 | 61 | // MARK: Iterator 62 | 63 | extension RemoveDuplicatesAsyncSequence { 64 | public struct Iterator: AsyncIteratorProtocol { 65 | private let predicate: Predicate 66 | private var iterator: Base.AsyncIterator 67 | private var previousElement: Base.Element? 68 | 69 | // MARK: Initialization 70 | 71 | init( 72 | base: Base, 73 | predicate: @escaping Predicate 74 | ) { 75 | self.iterator = base.makeAsyncIterator() 76 | self.predicate = predicate 77 | } 78 | 79 | // MARK: AsyncIteratorProtocol 80 | 81 | public mutating func next() async rethrows -> Element? { 82 | let element = try await self.iterator.next() 83 | let previousElement = self.previousElement 84 | 85 | // Update previous element 86 | self.previousElement = element 87 | 88 | guard let unwrappedElement = element, 89 | let unwrappedPreviousElement = previousElement else { return element } 90 | 91 | if self.predicate(unwrappedPreviousElement, unwrappedElement) { 92 | return try await self.next() 93 | } else { 94 | return element 95 | } 96 | } 97 | } 98 | } 99 | 100 | extension RemoveDuplicatesAsyncSequence.Iterator: Sendable 101 | where 102 | Base.AsyncIterator: Sendable, 103 | Base.Element: Sendable {} 104 | 105 | // MARK: Remove duplicates 106 | 107 | extension AsyncSequence where Element: Equatable { 108 | /// Emits only elements that don't match the previous element. 109 | /// - Returns: A `AsyncRemoveDuplicatesSequence` instance. 110 | public func removeDuplicates() -> RemoveDuplicatesAsyncSequence { 111 | .init(base: self) { $0 == $1 } 112 | } 113 | 114 | /// Omits any element that the predicate determines is equal to the previous element. 115 | /// - Parameter predicate: A closure to evaluate whether two elements are equivalent. 116 | /// Return true from this closure to indicate that the second element is a duplicate of the first. 117 | /// - Returns: A `AsyncRemoveDuplicatesSequence` instance. 118 | public func removeDuplicates( 119 | by predicate: @escaping RemoveDuplicatesAsyncSequence.Predicate 120 | ) -> RemoveDuplicatesAsyncSequence { 121 | .init(base: self, predicate: predicate) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/ReplaceErrorAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// An async sequence that replaces any errors in the sequence with a provided element. 2 | /// 3 | /// ```swift 4 | /// let sequence = Fail( 5 | /// error: TestError() 6 | /// ) 7 | /// .replaceError(with: 0) 8 | /// 9 | /// for await value in sequence { 10 | /// print(value) 11 | /// } 12 | /// 13 | /// // Prints: 14 | /// // 0 15 | /// ``` 16 | public struct ReplaceErrorAsyncSequence: AsyncSequence { 17 | /// The kind of elements streamed. 18 | public typealias Element = Base.Element 19 | 20 | // Private 21 | private let base: Base 22 | private let replacement: Element 23 | 24 | // MARK: Initialization 25 | 26 | /// Creates an async sequence that replaces any errors in the sequence with a provided element. 27 | /// - Parameters: 28 | /// - base: The async sequence in which this sequence receives it's elements. 29 | /// - output: The element with which to replace errors from the base async sequence. 30 | public init(base: Base, output: Element) { 31 | self.base = base 32 | self.replacement = output 33 | } 34 | 35 | // MARK: AsyncSequence 36 | 37 | /// Creates an async iterator that emits elements of this async sequence. 38 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 39 | public func makeAsyncIterator() -> Iterator { 40 | Iterator(base: self.base, output: self.replacement) 41 | } 42 | } 43 | 44 | extension ReplaceErrorAsyncSequence: Sendable 45 | where 46 | Base: Sendable, 47 | Base.Element: Sendable {} 48 | 49 | // MARK: Iterator 50 | 51 | extension ReplaceErrorAsyncSequence { 52 | public struct Iterator: AsyncIteratorProtocol { 53 | private let replacement: Element 54 | private var iterator: Base.AsyncIterator 55 | 56 | // MARK: Initialization 57 | 58 | init( 59 | base: Base, 60 | output: Element 61 | ) { 62 | self.iterator = base.makeAsyncIterator() 63 | self.replacement = output 64 | } 65 | 66 | // MARK: AsyncIteratorProtocol 67 | 68 | public mutating func next() async -> Element? { 69 | do { 70 | return try await self.iterator.next() 71 | } catch { 72 | return self.replacement 73 | } 74 | } 75 | } 76 | } 77 | 78 | extension ReplaceErrorAsyncSequence.Iterator: Sendable 79 | where 80 | Base.AsyncIterator: Sendable, 81 | Base.Element: Sendable {} 82 | 83 | // MARK: Replace error 84 | 85 | extension AsyncSequence { 86 | /// Replaces any errors in the async sequence with the provided element. 87 | /// 88 | /// ```swift 89 | /// let sequence = Fail( 90 | /// error: TestError() 91 | /// ) 92 | /// .replaceError(with: 0) 93 | /// 94 | /// for await value in sequence { 95 | /// print(value) 96 | /// } 97 | /// 98 | /// // Prints: 99 | /// // 0 100 | /// ``` 101 | /// - Parameter output: The element with which to replace errors from the base async sequence. 102 | /// - Returns: A `ReplaceErrorAsyncSequence` instance. 103 | public func replaceError(with output: Element) -> ReplaceErrorAsyncSequence { 104 | .init(base: self, output: output) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/SequenceAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An async version of `Sequence`. Generally used to turn any `Sequence` into 4 | /// it's async counterpart. 5 | /// 6 | /// ```swift 7 | /// let sequence = [0, 1, 2, 3].async 8 | /// 9 | /// for await value in sequence { 10 | /// print(value) 11 | /// } 12 | /// 13 | /// // Prints: 14 | /// // 1 15 | /// // 2 16 | /// // 3 17 | /// ``` 18 | public struct SequenceAsyncSequence: AsyncSequence { 19 | /// The kind of elements streamed. 20 | public typealias Element = P.Element 21 | 22 | // Private 23 | private let sequence: P 24 | 25 | // MARK: Initialization 26 | 27 | /// Creates an async sequence that combines the two async sequence. 28 | /// - Parameters: 29 | /// - p: A sequence. 30 | public init(_ sequence: P) { 31 | self.sequence = sequence 32 | } 33 | 34 | // MARK: AsyncSequence 35 | 36 | /// Creates an async iterator that emits elements of this async sequence. 37 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 38 | public func makeAsyncIterator() -> Iterator { 39 | .init(self.sequence.makeIterator()) 40 | } 41 | } 42 | 43 | extension SequenceAsyncSequence: Sendable where P: Sendable {} 44 | 45 | // MARK: Iterator 46 | 47 | extension SequenceAsyncSequence { 48 | public struct Iterator: AsyncIteratorProtocol { 49 | private var iterator: P.Iterator 50 | 51 | // MARK: Initialization 52 | 53 | init(_ iterator: P.Iterator) { 54 | self.iterator = iterator 55 | } 56 | 57 | // MARK: AsyncIteratorProtocol 58 | 59 | /// Produces the next element in the sequence. 60 | /// - Returns: The next element or `nil` if the end of the sequence is reached. 61 | public mutating func next() async -> P.Element? { 62 | self.iterator.next() 63 | } 64 | } 65 | } 66 | 67 | extension SequenceAsyncSequence.Iterator: Sendable where P.Iterator: Sendable {} 68 | 69 | // MARK: Sequence 70 | 71 | public extension Sequence { 72 | /// An async sequence that contains all the elements of 73 | /// the current sequence. 74 | /// 75 | /// ```swift 76 | /// let sequence = [0, 1, 2, 3].async 77 | /// 78 | /// for await value in sequence { 79 | /// print(value) 80 | /// } 81 | /// 82 | /// // Prints: 83 | /// // 1 84 | /// // 2 85 | /// // 3 86 | /// ``` 87 | var async: SequenceAsyncSequence { 88 | .init(self) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/SharedAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An async sequence that can be shared between multiple tasks. 4 | /// 5 | /// ```swift 6 | /// let values = [ 7 | /// "a", 8 | /// "ab", 9 | /// "abc", 10 | /// "abcd" 11 | /// ] 12 | /// 13 | /// let stream = AsyncStream { continuation in 14 | /// for value in values { 15 | /// continuation.yield(value) 16 | /// } 17 | /// continuation.finish() 18 | /// } 19 | /// .shared() 20 | /// 21 | /// Task { 22 | /// let values = try await self.stream.collect() 23 | /// // ... 24 | /// } 25 | /// 26 | /// Task.detached { 27 | /// let values = try await self.stream.collect() 28 | /// // ... 29 | /// } 30 | /// 31 | /// let values = try await self.stream.collect() 32 | /// // ... 33 | /// ``` 34 | public struct SharedAsyncSequence: AsyncSequence, Sendable where Base: Sendable { 35 | /// The type of async iterator. 36 | public typealias AsyncIterator = AsyncThrowingStream.Iterator 37 | 38 | /// The type of elements streamed. 39 | public typealias Element = Base.Element 40 | 41 | // Private 42 | private var base: Base 43 | private let manager: SubSequenceManager 44 | 45 | // MARK: SharedAsyncSequence (Public Properties) 46 | 47 | /// Creates a shareable async sequence that can be used across multiple tasks. 48 | /// - Parameters: 49 | /// - base: The async sequence in which this sequence receives it's elements. 50 | public init(_ base: Base) { 51 | self.base = base 52 | self.manager = SubSequenceManager(base) 53 | } 54 | 55 | // MARK: AsyncSequence 56 | 57 | /// Creates an async iterator that emits elements of this async sequence. 58 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 59 | public func makeAsyncIterator() -> AsyncThrowingStream.Iterator { 60 | self.manager.makeAsyncIterator() 61 | } 62 | } 63 | 64 | // MARK: CurrentElementAsyncSequence extension 65 | 66 | extension SharedAsyncSequence { 67 | /// Yield a new element to the sequence. 68 | /// 69 | /// Yielding a new element will update this async sequence's `element` property 70 | /// along with emitting it through the sequence. 71 | /// - Parameter element: The element to yield. 72 | public func yield(_ element: Element) async 73 | where Base == CurrentElementAsyncSequence, Element: Sendable { 74 | await self.base.yield(element) 75 | } 76 | 77 | /// Mark the sequence as finished by having it's iterator emit nil. 78 | /// 79 | /// Once finished, any calls to yield will result in no change. 80 | public func finish() async where Base == CurrentElementAsyncSequence { 81 | await self.base.finish() 82 | } 83 | 84 | /// Emit one last element beford marking the sequence as finished by having it's iterator emit nil. 85 | /// 86 | /// Once finished, any calls to yield will result in no change. 87 | /// - Parameter element: The element to emit. 88 | public func finish(with element: Element) async 89 | where Base == CurrentElementAsyncSequence, Element: Sendable { 90 | await self.base.finish(with: element) 91 | } 92 | 93 | /// The element wrapped by this async sequence, emitted as a new element whenever it changes. 94 | public func element() async -> Element 95 | where Base == CurrentElementAsyncSequence, Element: Sendable { 96 | await self.base.element 97 | } 98 | } 99 | 100 | // MARK: PassthroughAsyncSequence extension 101 | 102 | extension SharedAsyncSequence { 103 | /// Yield a new element to the sequence. 104 | /// 105 | /// Yielding a new element will emit it through the sequence. 106 | /// - Parameter element: The element to yield. 107 | public func yield(_ element: Element) where Base == PassthroughAsyncSequence { 108 | self.base.yield(element) 109 | } 110 | 111 | /// Mark the sequence as finished by having it's iterator emit nil. 112 | /// 113 | /// Once finished, any calls to yield will result in no change. 114 | public func finish() where Base == PassthroughAsyncSequence { 115 | self.base.finish() 116 | } 117 | 118 | /// Emit one last element beford marking the sequence as finished by having it's iterator emit nil. 119 | /// 120 | /// Once finished, any calls to yield will result in no change. 121 | /// - Parameter element: The element to emit. 122 | public func finish(with element: Element) where Base == PassthroughAsyncSequence { 123 | self.base.finish(with: element) 124 | } 125 | } 126 | 127 | // MARK: Sub sequence manager 128 | 129 | fileprivate actor SubSequenceManager where Base: Sendable { 130 | fileprivate typealias Element = Base.Element 131 | 132 | // Private 133 | private var base: Base 134 | private var continuations: [String : AsyncThrowingStream.Continuation] = [:] 135 | private var subscriptionTask: Task? 136 | 137 | // MARK: Initialization 138 | 139 | fileprivate init(_ base: Base) { 140 | self.base = base 141 | } 142 | 143 | deinit { 144 | self.subscriptionTask?.cancel() 145 | } 146 | 147 | // MARK: API 148 | 149 | /// Creates an new stream and returns its async iterator that emits elements of base async sequence. 150 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 151 | nonisolated fileprivate func makeAsyncIterator() -> ThrowingPassthroughAsyncSequence.AsyncIterator { 152 | let id = UUID().uuidString 153 | let sequence = AsyncThrowingStream { 154 | $0.onTermination = { @Sendable _ in 155 | self.remove(id) 156 | } 157 | 158 | await self.add(id: id, continuation: $0) 159 | } 160 | 161 | return sequence.makeAsyncIterator() 162 | } 163 | 164 | // MARK: Sequence management 165 | 166 | nonisolated private func remove(_ id: String) { 167 | Task { 168 | await self._remove(id) 169 | } 170 | } 171 | 172 | private func _remove(_ id: String) { 173 | self.continuations.removeValue(forKey: id) 174 | } 175 | 176 | private func add(id: String, continuation: AsyncThrowingStream.Continuation) { 177 | self.continuations[id] = continuation 178 | self.subscribeToBaseSequenceIfNeeded() 179 | } 180 | 181 | private func subscribeToBaseSequenceIfNeeded() { 182 | guard self.subscriptionTask == nil else { return } 183 | 184 | self.subscriptionTask = Task { [weak self, base] in 185 | guard let self = self else { return } 186 | 187 | guard !Task.isCancelled else { 188 | await self.continuations.values.forEach { 189 | $0.finish(throwing: CancellationError()) 190 | } 191 | return 192 | } 193 | 194 | do { 195 | for try await value in base { 196 | await self.continuations.values.forEach { $0.yield(value) } 197 | } 198 | 199 | await self.continuations.values.forEach { $0.finish() } 200 | } catch { 201 | await self.continuations.values.forEach { $0.finish(throwing: error) } 202 | } 203 | } 204 | } 205 | } 206 | 207 | // MARK: Shared 208 | 209 | extension AsyncSequence { 210 | /// Creates a shareable async sequence that can be used across multiple tasks. 211 | public func shared() -> SharedAsyncSequence where Self: Sendable { 212 | .init(self) 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/ThrottleAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An async sequence that emits either the most-recent or first element emitted 4 | /// by the base async sequence in a specified time interval. 5 | /// 6 | /// ThrottleAsyncSequence selectively emits elements from a base async sequence during an 7 | /// interval you specify. Other elements received within the throttling interval aren’t emitted. 8 | /// 9 | /// ```swift 10 | /// let sequence = AsyncStream { continuation in 11 | /// continuation.yield(0) 12 | /// try? await Task.sleep(nanoseconds: 100_000_000) 13 | /// continuation.yield(1) 14 | /// try? await Task.sleep(nanoseconds: 100_000_000) 15 | /// continuation.yield(2) 16 | /// continuation.yield(3) 17 | /// continuation.yield(4) 18 | /// continuation.yield(5) 19 | /// continuation.finish() 20 | /// } 21 | /// 22 | /// for element in try await sequence.throttle(for: 0.05, latest: true) { 23 | /// print(element) 24 | /// } 25 | /// 26 | /// // Prints: 27 | /// // 0 28 | /// // 1 29 | /// // 2 30 | /// ``` 31 | public struct ThrottleAsyncSequence: AsyncSequence { 32 | /// The kind of elements streamed. 33 | public typealias Element = T.Element 34 | 35 | // Private 36 | private var base: T 37 | private var interval: TimeInterval 38 | private var latest: Bool 39 | 40 | // MARK: Initialization 41 | 42 | /// Creates an async sequence that emits either the most-recent or first element 43 | /// emitted by the base async sequence in a specified time interval. 44 | /// - Parameters: 45 | /// - base: The async sequence in which this sequence receives it's elements. 46 | /// - interval: The interval in which to emit the most recent element. 47 | /// - latest: A Boolean value indicating whether to emit the most recent element. 48 | /// If false, the async sequence emits the first element received during the interval. 49 | public init( 50 | _ base: T, 51 | interval: TimeInterval, 52 | latest: Bool 53 | ) { 54 | self.base = base 55 | self.interval = interval 56 | self.latest = latest 57 | } 58 | 59 | // MARK: AsyncSequence 60 | 61 | /// Creates an async iterator that emits elements of this async sequence. 62 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 63 | public func makeAsyncIterator() -> Iterator { 64 | Iterator(base: self.base.makeAsyncIterator(), interval: self.interval, latest: self.latest) 65 | } 66 | } 67 | 68 | extension ThrottleAsyncSequence: Sendable 69 | where 70 | T: Sendable {} 71 | 72 | // MARK: Iterator 73 | 74 | extension ThrottleAsyncSequence { 75 | public struct Iterator: AsyncIteratorProtocol { 76 | var base: T.AsyncIterator 77 | var interval: TimeInterval 78 | var latest: Bool 79 | 80 | // Private 81 | private var collectedElements: [Element] = [] 82 | private var lastEmission: Date? 83 | 84 | // MARK: Initialization 85 | 86 | init( 87 | base: T.AsyncIterator, 88 | interval: TimeInterval, 89 | latest: Bool 90 | ) 91 | { 92 | self.base = base 93 | self.interval = interval 94 | self.latest = latest 95 | } 96 | 97 | // MARK: AsyncIteratorProtocol 98 | 99 | public mutating func next() async rethrows -> Element? { 100 | while true { 101 | guard let value = try await self.base.next() else { 102 | return nil 103 | } 104 | 105 | guard let lastEmission = self.lastEmission else { 106 | self.lastEmission = Date() 107 | return value 108 | } 109 | 110 | self.collectedElements.append(value) 111 | let element = (self.latest ? self.collectedElements.last : self.collectedElements.first) ?? value 112 | let gap = Date().timeIntervalSince(lastEmission) 113 | if gap >= self.interval { 114 | self.lastEmission = Date() 115 | self.collectedElements.removeAll() 116 | return element 117 | } 118 | } 119 | } 120 | } 121 | } 122 | 123 | extension ThrottleAsyncSequence.Iterator: Sendable 124 | where 125 | T.AsyncIterator: Sendable, 126 | T.Element: Sendable {} 127 | 128 | // MARK: Throttle 129 | 130 | extension AsyncSequence { 131 | 132 | /// Emits either the most-recent or first element emitted by the base async 133 | /// sequence in the specified time interval. 134 | /// 135 | /// ThrottleAsyncSequence selectively emits elements from a base async sequence during an 136 | /// interval you specify. Other elements received within the throttling interval aren’t emitted. 137 | /// 138 | /// ```swift 139 | /// let sequence = AsyncStream { continuation in 140 | /// continuation.yield(0) 141 | /// try? await Task.sleep(nanoseconds: 100_000_000) 142 | /// continuation.yield(1) 143 | /// try? await Task.sleep(nanoseconds: 100_000_000) 144 | /// continuation.yield(2) 145 | /// continuation.yield(3) 146 | /// continuation.yield(4) 147 | /// continuation.yield(5) 148 | /// continuation.finish() 149 | /// } 150 | /// 151 | /// for element in try await sequence.throttle(for: 0.05, latest: true) { 152 | /// print(element) 153 | /// } 154 | /// 155 | /// // Prints: 156 | /// // 0 157 | /// // 1 158 | /// // 2 159 | /// ``` 160 | /// - Parameters: 161 | /// - interval: The interval in which to emit the most recent element. 162 | /// - latest: A Boolean value indicating whether to emit the most recent element. 163 | /// - Returns: A `ThrottleAsyncSequence` instance. 164 | public func throttle(for interval: TimeInterval, latest: Bool) -> ThrottleAsyncSequence { 165 | .init(self, interval: interval, latest: latest) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/ThrowingPassthroughAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// A async sequence that broadcasts elements. 2 | /// 3 | /// ```swift 4 | /// let sequence = ThrowingPassthroughAsyncSequence() 5 | /// sequence.yield(0) 6 | /// sequence.yield(1) 7 | /// sequence.yield(2) 8 | /// sequence.finish(throwing: TestError()) 9 | /// 10 | /// do { 11 | /// for try await value in sequence { 12 | /// print(value) 13 | /// } 14 | /// } catch { 15 | /// print("Error!") 16 | /// } 17 | /// 18 | /// // Prints: 19 | /// // 0 20 | /// // 1 21 | /// // 2 22 | /// // Error! 23 | /// ``` 24 | public struct ThrowingPassthroughAsyncSequence: AsyncSequence { 25 | private var stream: AsyncThrowingStream! 26 | private var continuation: AsyncThrowingStream.Continuation! 27 | 28 | // MARK: Initialization 29 | 30 | /// Creates an async sequence that broadcasts elements. 31 | public init() { 32 | self.stream = .init { self.continuation = $0 } 33 | } 34 | 35 | // MARK: AsyncSequence 36 | 37 | /// Creates an async iterator that emits elements of this async sequence. 38 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 39 | public func makeAsyncIterator() -> AsyncThrowingStream.Iterator { 40 | self.stream.makeAsyncIterator() 41 | } 42 | 43 | // MARK: API 44 | 45 | /// Yield a new element to the sequence. 46 | /// 47 | /// Yielding a new element will update this async sequence's `element` property 48 | /// along with emitting it through the sequence. 49 | /// - Parameter element: The element to yield. 50 | public func yield(_ element: Element) { 51 | self.continuation.yield(element) 52 | } 53 | 54 | /// Mark the sequence as finished by having it's iterator emit nil. 55 | /// 56 | /// Once finished, any calls to yield will result in no change. 57 | public func finish() { 58 | self.continuation.finish() 59 | } 60 | 61 | /// Mark the sequence as finished by having it's iterator throw the provided error. 62 | /// 63 | /// Once finished, any calls to yield will result in no change. 64 | /// - Parameter error: The error to throw. 65 | public func finish(throwing error: Error) { 66 | self.continuation.finish(throwing: error) 67 | } 68 | 69 | /// Emit one last element beford marking the sequence as finished by having it's iterator emit nil. 70 | /// 71 | /// Once finished, any calls to yield will result in no change. 72 | /// - Parameter element: The element to emit. 73 | public func finish(with element: Element) { 74 | self.continuation.finish(with: element) 75 | } 76 | } 77 | 78 | extension ThrowingPassthroughAsyncSequence: Sendable where Element: Sendable {} 79 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/TimerAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An async sequence that emits the current date on a given interval. 4 | /// 5 | /// ```swift 6 | /// let sequence = TimerAsyncSequence(interval: 1) 7 | /// 8 | /// let start = Date.now 9 | /// for await element in sequence { 10 | /// print(element) 11 | /// } 12 | /// 13 | /// // Prints: 14 | /// // 2022-03-19 20:49:30 +0000 15 | /// // 2022-03-19 20:49:31 +0000 16 | /// // 2022-03-19 20:49:32 +0000 17 | /// ``` 18 | public final class TimerAsyncSequence: AsyncSequence { 19 | /// The kind of elements streamed. 20 | public typealias Element = Date 21 | 22 | // Private 23 | private let interval: TimeInterval 24 | private let passthroughSequence: PassthroughAsyncSequence = .init() 25 | private var task: Task? 26 | 27 | // MARK: Initialization 28 | 29 | /// Creates an async sequence that emits the current date on a given interval. 30 | /// - Parameters: 31 | /// - interval: The interval on which to emit elements. 32 | public init(interval: TimeInterval) { 33 | self.interval = interval 34 | } 35 | 36 | // MARK: Timer 37 | 38 | private func start() { 39 | self.task = Task { [interval, passthroughSequence] in 40 | do { 41 | while !Task.isCancelled { 42 | try await Task.sleep(seconds: interval) 43 | passthroughSequence.yield(Date()) 44 | } 45 | } catch is CancellationError { 46 | passthroughSequence.finish() 47 | } catch { 48 | throw error 49 | } 50 | } 51 | } 52 | 53 | /// Cancel the sequence from emitting anymore elements. 54 | public func cancel() { 55 | self.task?.cancel() 56 | } 57 | 58 | // MARK: AsyncSequence 59 | 60 | /// Creates an async iterator that emits elements of this async sequence. 61 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 62 | public func makeAsyncIterator() -> PassthroughAsyncSequence.AsyncIterator { 63 | defer { self.start() } 64 | return self.passthroughSequence.makeAsyncIterator() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/Zip3AsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that applys a zip function to the three async sequences. 2 | /// 3 | /// Use `Zip3AsyncSequence` to combine the latest elements from three async sequcnes and emit a tuple. 4 | /// 5 | /// The async sequence waits until both provided async sequences have emitted an element, 6 | /// then emits both elements as a tuple. 7 | /// 8 | /// If one sequence never emits a value or raises an error then the zipped sequence will finish. 9 | /// 10 | /// ```swift 11 | /// let streamA = .init { continuation in 12 | /// continuation.yield(1) 13 | /// continuation.yield(2) 14 | /// continuation.finish() 15 | /// } 16 | /// 17 | /// let streamB = .init { continuation in 18 | /// continuation.yield(5) 19 | /// continuation.yield(6) 20 | /// continuation.yield(7) 21 | /// continuation.finish() 22 | /// } 23 | /// 24 | /// let streamC = .init { continuation in 25 | /// continuation.yield(8) 26 | /// continuation.yield(9) 27 | /// continuation.finish() 28 | /// } 29 | /// 30 | /// for await value in streamA.zip(streamB, streamC) { 31 | /// print(value) 32 | /// } 33 | /// 34 | /// // Prints: 35 | /// // (1, 5, 8) 36 | /// // (2, 6, 9) 37 | /// ``` 38 | public struct Zip3AsyncSequence: AsyncSequence { 39 | /// The kind of elements streamed. 40 | public typealias Element = (P.Element, Q.Element, R.Element) 41 | 42 | // Private 43 | private let p: P 44 | private let q: Q 45 | private let r: R 46 | 47 | private var iteratorP: P.AsyncIterator 48 | private var iteratorQ: Q.AsyncIterator 49 | private var iteratorR: R.AsyncIterator 50 | 51 | // MARK: Initialization 52 | 53 | /// Creates an async sequence that zips and emits the elements from the provided async sequences. 54 | /// - Parameters: 55 | /// - p: An async sequence. 56 | /// - q: An async sequence. 57 | /// - r: An async sequence. 58 | init( 59 | _ p: P, 60 | _ q: Q, 61 | _ r: R 62 | ) { 63 | self.p = p 64 | self.iteratorP = p.makeAsyncIterator() 65 | 66 | self.q = q 67 | self.iteratorQ = q.makeAsyncIterator() 68 | 69 | self.r = r 70 | self.iteratorR = r.makeAsyncIterator() 71 | } 72 | 73 | // MARK: AsyncSequence 74 | 75 | /// Creates an async iterator that emits elements of this async sequence. 76 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 77 | public func makeAsyncIterator() -> Self { 78 | .init(self.p, self.q, self.r) 79 | } 80 | } 81 | 82 | extension Zip3AsyncSequence: Sendable 83 | where 84 | P: Sendable, 85 | P.AsyncIterator: Sendable, 86 | Q: Sendable, 87 | Q.AsyncIterator: Sendable, 88 | R: Sendable, 89 | R.AsyncIterator: Sendable {} 90 | 91 | // MARK: AsyncIteratorProtocol 92 | 93 | extension Zip3AsyncSequence: AsyncIteratorProtocol { 94 | /// Produces the next element in the sequence. 95 | /// 96 | /// Continues to call `next()` on it's base iterator and iterator of 97 | /// it's combined sequence. 98 | /// 99 | /// If any iterator returns `nil`, indicating the end of the sequence, this 100 | /// iterator returns `nil`. 101 | /// - Returns: The next element or `nil` if the end of the sequence is reached. 102 | public mutating func next() async rethrows -> Element? { 103 | let elementP = try await self.iteratorP.next() 104 | let elementQ = try await self.iteratorQ.next() 105 | let elementR = try await self.iteratorR.next() 106 | 107 | // If any sequence reaches the end, then this sequence finishes. 108 | guard 109 | let unwrappedElementP = elementP, 110 | let unwrappedElementQ = elementQ, 111 | let unwrappedElementR = elementR else { 112 | return nil 113 | } 114 | 115 | return (unwrappedElementP, unwrappedElementQ, unwrappedElementR) 116 | } 117 | } 118 | 119 | // MARK: Zip 120 | 121 | extension AsyncSequence { 122 | /// Create an asynchronous sequence that applys a zip function to the three async sequences. 123 | /// 124 | /// Combines the latest elements from three async sequences and emits a tuple. 125 | /// 126 | /// The async sequence waits until both provided async sequences have emitted an element, 127 | /// then emits both elements as a tuple. 128 | /// 129 | /// If one sequence never emits a value or raises an error then the zipped sequence will finish. 130 | /// 131 | /// ```swift 132 | /// let streamA = .init { continuation in 133 | /// continuation.yield(1) 134 | /// continuation.yield(2) 135 | /// continuation.finish() 136 | /// } 137 | /// 138 | /// let streamB = .init { continuation in 139 | /// continuation.yield(5) 140 | /// continuation.yield(6) 141 | /// continuation.yield(7) 142 | /// continuation.finish() 143 | /// } 144 | /// 145 | /// let streamC = .init { continuation in 146 | /// continuation.yield(8) 147 | /// continuation.yield(9) 148 | /// continuation.finish() 149 | /// } 150 | /// 151 | /// for await value in streamA.zip(streamB, streamC) { 152 | /// print(value) 153 | /// } 154 | /// 155 | /// // Prints: 156 | /// // (1, 5, 8) 157 | /// // (2, 6, 9) 158 | /// ``` 159 | /// - Parameters: 160 | /// - q: Another async sequence. 161 | /// - r: Another async sequence. 162 | /// - Returns: A async sequence zips elements from this and another async sequence. 163 | public func zip( 164 | _ q: Q, 165 | _ r: R 166 | ) -> Zip3AsyncSequence where Q: AsyncSequence, R: AsyncSequence { 167 | .init(self, q, r) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Sources/Asynchrone/Sequences/ZipAsyncSequence.swift: -------------------------------------------------------------------------------- 1 | /// An asynchronous sequence that applys a zip function to the two async sequences. 2 | /// 3 | /// Use `ZipAsyncSequence` to combine the latest elements from two async sequences and emit a tuple. 4 | /// 5 | /// The async sequence waits until both provided async sequences have emitted an element, 6 | /// then emits both elements as a tuple. 7 | /// 8 | /// If one sequence never emits a value or raises an error then the zipped sequence will finish. 9 | /// 10 | /// ```swift 11 | /// let streamA = .init { continuation in 12 | /// continuation.yield(1) 13 | /// continuation.yield(2) 14 | /// continuation.finish() 15 | /// } 16 | /// 17 | /// let streamB = .init { continuation in 18 | /// continuation.yield(5) 19 | /// continuation.yield(6) 20 | /// continuation.yield(7) 21 | /// continuation.finish() 22 | /// } 23 | /// 24 | /// for await value in streamA.zip(streamB) { 25 | /// print(value) 26 | /// } 27 | /// 28 | /// // Prints: 29 | /// // (1, 5) 30 | /// // (2, 6) 31 | /// ``` 32 | public struct ZipAsyncSequence: AsyncSequence { 33 | /// The kind of elements streamed. 34 | public typealias Element = (P.Element, Q.Element) 35 | 36 | // Private 37 | private let p: P 38 | private let q: Q 39 | 40 | private var iteratorP: P.AsyncIterator 41 | private var iteratorQ: Q.AsyncIterator 42 | 43 | // MARK: Initialization 44 | 45 | /// Creates an async sequence that zips and emits the elements from the provided async sequences. 46 | /// - Parameters: 47 | /// - p: An async sequence. 48 | /// - q: An async sequence. 49 | init( 50 | _ p: P, 51 | _ q: Q 52 | ) { 53 | self.p = p 54 | self.iteratorP = p.makeAsyncIterator() 55 | 56 | self.q = q 57 | self.iteratorQ = q.makeAsyncIterator() 58 | } 59 | 60 | // MARK: AsyncSequence 61 | 62 | /// Creates an async iterator that emits elements of this async sequence. 63 | /// - Returns: An instance that conforms to `AsyncIteratorProtocol`. 64 | public func makeAsyncIterator() -> Self { 65 | .init(self.p, self.q) 66 | } 67 | } 68 | 69 | extension ZipAsyncSequence: Sendable 70 | where 71 | P: Sendable, 72 | P.AsyncIterator: Sendable, 73 | Q: Sendable, 74 | Q.AsyncIterator: Sendable {} 75 | 76 | // MARK: AsyncIteratorProtocol 77 | 78 | extension ZipAsyncSequence: AsyncIteratorProtocol { 79 | /// Produces the next element in the sequence. 80 | /// 81 | /// Continues to call `next()` on it's base iterator and iterator of 82 | /// it's combined sequence. 83 | /// 84 | /// If any iterator returns `nil`, indicating the end of the sequence, this 85 | /// iterator returns `nil`. 86 | /// - Returns: The next element or `nil` if the end of the sequence is reached. 87 | public mutating func next() async rethrows -> Element? { 88 | let elementP = try await self.iteratorP.next() 89 | let elementQ = try await self.iteratorQ.next() 90 | 91 | // If any sequence reaches the end, then this sequence finishes. 92 | guard 93 | let unwrappedElementP = elementP, 94 | let unwrappedElementQ = elementQ else { 95 | return nil 96 | } 97 | 98 | return (unwrappedElementP, unwrappedElementQ) 99 | } 100 | } 101 | 102 | // MARK: Zip 103 | 104 | extension AsyncSequence { 105 | /// Create an asynchronous sequence that applys a zip function to the two async sequences. 106 | /// 107 | /// Combines the latest elements from two async sequences and emits a tuple. 108 | /// 109 | /// The async sequence waits until both provided async sequences have emitted an element, 110 | /// then emits both elements as a tuple. 111 | /// 112 | /// If one sequence never emits a value or raises an error then the zipped sequence will finish. 113 | /// 114 | /// ```swift 115 | /// let streamA = .init { continuation in 116 | /// continuation.yield(1) 117 | /// continuation.yield(2) 118 | /// continuation.finish() 119 | /// } 120 | /// 121 | /// let streamB = .init { continuation in 122 | /// continuation.yield(5) 123 | /// continuation.yield(6) 124 | /// continuation.yield(7) 125 | /// continuation.finish() 126 | /// } 127 | /// 128 | /// for await value in streamA.zip(streamB) { 129 | /// print(value) 130 | /// } 131 | /// 132 | /// // Prints: 133 | /// // (1, 5) 134 | /// // (2, 6) 135 | /// ``` 136 | /// - Parameters: 137 | /// - other: Another async sequence to zip with. 138 | /// - Returns: A async sequence zips elements from this and another async sequence. 139 | public func zip( 140 | _ other: Q 141 | ) -> ZipAsyncSequence where Q: AsyncSequence { 142 | .init(self, other) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Assertion.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | /// Asserts that an async expression is not `nil`, and returns its unwrapped value. 4 | /// 5 | /// Generates a failure when `expression == nil`. 6 | /// 7 | /// - Parameters: 8 | /// - expression: An expression of type `T?` to compare against `nil`. 9 | /// Its type will determine the type of the returned value. 10 | /// - message: An optional description of the failure. 11 | /// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called. 12 | /// - line: The line number on which failure occurred. Defaults to the line number on which this function was called. 13 | /// - Returns: A value of type `T`, the result of evaluating and unwrapping the given `expression`. 14 | /// - Throws: An error when `expression == nil`. It will also rethrow any error thrown while evaluating the given expression. 15 | func XCTAsyncUnwrap( 16 | _ expression: () async throws -> T?, 17 | _ message: @autoclosure () -> String = "", 18 | file: StaticString = #filePath, 19 | line: UInt = #line 20 | ) async throws -> T { 21 | let value = try await expression() 22 | return try XCTUnwrap(value, message(), file: file, line: line) 23 | } 24 | 25 | /// Assert two expressions are eventually equal. 26 | /// - Parameters: 27 | /// - expressionA: Expression A 28 | /// - expressionB: Expression B 29 | /// - timeout: Time to wait for store state changes. Defaults to `5` 30 | /// - file: The file where this assertion is being called. Defaults to `#filePath`. 31 | /// - line: The line in the file where this assertion is being called. Defaults to `#line`. 32 | func XCTAssertEventuallyEqual( 33 | _ expressionA: @autoclosure @escaping () -> T?, 34 | _ expressionB: @autoclosure @escaping () -> T?, 35 | timeout: TimeInterval = 5.0, 36 | file: StaticString = #filePath, 37 | line: UInt = #line 38 | ) async { 39 | let timeoutDate = Date(timeIntervalSinceNow: timeout) 40 | 41 | while true { 42 | let resultA = expressionA() 43 | let resultB = expressionB() 44 | 45 | switch resultA == resultB { 46 | // All good! 47 | case true: 48 | return 49 | // False and timed out. 50 | case false where Date().compare(timeoutDate) == .orderedDescending: 51 | let error = XCTAssertEventuallyEqualError( 52 | resultA: resultA, 53 | resultB: resultB 54 | ) 55 | 56 | XCTFail( 57 | error.message, 58 | file: file, 59 | line: line 60 | ) 61 | return 62 | // False but still within timeout limit. 63 | case false: 64 | try? await Task.sleep(nanoseconds: 1000000) 65 | } 66 | } 67 | } 68 | 69 | /// Assert two async expressions are eventually equal. 70 | /// - Parameters: 71 | /// - expressionA: Expression A 72 | /// - expressionB: Expression B 73 | /// - timeout: Time to wait for store state changes. Defaults to `5` 74 | /// - file: The file where this assertion is being called. Defaults to `#filePath`. 75 | /// - line: The line in the file where this assertion is being called. Defaults to `#line`. 76 | func XCTAssertEventuallyEqual( 77 | _ expressionA: @escaping () async -> T?, 78 | _ expressionB: @escaping () async -> T?, 79 | timeout: TimeInterval = 5.0, 80 | file: StaticString = #filePath, 81 | line: UInt = #line 82 | ) async { 83 | let timeoutDate = Date(timeIntervalSinceNow: timeout) 84 | 85 | while true { 86 | let resultA = await expressionA() 87 | let resultB = await expressionB() 88 | 89 | switch resultA == resultB { 90 | // All good! 91 | case true: 92 | return 93 | // False and timed out. 94 | case false where Date().compare(timeoutDate) == .orderedDescending: 95 | let error = XCTAssertEventuallyEqualError( 96 | resultA: resultA, 97 | resultB: resultB 98 | ) 99 | 100 | XCTFail( 101 | error.message, 102 | file: file, 103 | line: line 104 | ) 105 | return 106 | // False but still within timeout limit. 107 | case false: 108 | try? await Task.sleep(nanoseconds: 1000000) 109 | } 110 | } 111 | } 112 | 113 | /// Assert a value is eventually true. 114 | /// - Parameters: 115 | /// - expression: The value to assert eventually is true. 116 | /// - timeout: Time to wait for store state changes. Defaults to `5` 117 | /// - file: The file where this assertion is being called. Defaults to `#filePath`. 118 | /// - line: The line in the file where this assertion is being called. Defaults to `#line`. 119 | func XCTAssertEventuallyTrue( 120 | _ expression: @escaping @autoclosure () -> Bool, 121 | timeout: TimeInterval = 5.0, 122 | file: StaticString = #filePath, 123 | line: UInt = #line 124 | ) async { 125 | await XCTAssertEventuallyEqual( 126 | expression(), 127 | true, 128 | timeout: timeout, 129 | file: file, 130 | line: line 131 | ) 132 | } 133 | 134 | /// Assert an async closure thorws an error. 135 | /// - Parameters: 136 | /// - closure: The closure. 137 | /// - file: The file where this assertion is being called. Defaults to `#filePath`. 138 | /// - line: The line in the file where this assertion is being called. Defaults to `#line`. 139 | func XCTAsyncAssertThrow( 140 | _ closure: () async throws -> T, 141 | file: StaticString = #filePath, 142 | line: UInt = #line 143 | ) async { 144 | do { 145 | _ = try await closure() 146 | XCTFail( 147 | "Failed to throw error", 148 | file: file, 149 | line: line 150 | ) 151 | } catch {} 152 | } 153 | 154 | /// Assert an async closure does not throw. 155 | /// - Parameters: 156 | /// - closure: The closure. 157 | /// - file: The file where this assertion is being called. Defaults to `#filePath`. 158 | /// - line: The line in the file where this assertion is being called. Defaults to `#line`. 159 | func XCTAsyncAssertNoThrow( 160 | _ closure: () async throws -> T, 161 | file: StaticString = #filePath, 162 | line: UInt = #line 163 | ) async { 164 | do { 165 | _ = try await closure() 166 | } catch { 167 | XCTFail( 168 | "Unexpexted error thrown \(error)", 169 | file: file, 170 | line: line 171 | ) 172 | } 173 | } 174 | 175 | /// Assert an async closure returns nil. 176 | /// - Parameters: 177 | /// - closure: The closure. 178 | /// - file: The file where this assertion is being called. Defaults to `#filePath`. 179 | /// - line: The line in the file where this assertion is being called. Defaults to `#line`. 180 | func XCTAsyncAssertNil( 181 | _ closure: () async -> T?, 182 | file: StaticString = #filePath, 183 | line: UInt = #line 184 | ) async { 185 | let value = await closure() 186 | XCTAssertNil( 187 | value, 188 | file: file, 189 | line: line 190 | ) 191 | } 192 | 193 | /// Assert two async closures return equal values. 194 | /// - Parameters: 195 | /// - expressionA: Expression A. 196 | /// - expressionB: Expression B. 197 | /// - file: The file where this assertion is being called. Defaults to `#filePath`. 198 | /// - line: The line in the file where this assertion is being called. Defaults to `#line`. 199 | func XCTAsyncAssertEqual( 200 | _ expressionA: @escaping () async -> T, 201 | _ expressionB: @escaping () async -> T, 202 | file: StaticString = #filePath, 203 | line: UInt = #line 204 | ) async { 205 | let valueA = await expressionA() 206 | let valueB = await expressionB() 207 | 208 | XCTAssertEqual( 209 | valueA, 210 | valueB, 211 | file: file, 212 | line: line 213 | ) 214 | } 215 | 216 | // MARK: XCTAssertEventuallyEqualError 217 | 218 | struct XCTAssertEventuallyEqualError: Error { 219 | let message: String 220 | 221 | var localizedDescription: String { 222 | message 223 | } 224 | 225 | // MARK: Initialization 226 | 227 | init(resultA: T?, resultB: T?) { 228 | var resultADescription = "(null)" 229 | if let resultA = resultA { 230 | resultADescription = String(describing: resultA) 231 | } 232 | 233 | var resultBDescription = "(null)" 234 | if let resultB = resultB { 235 | resultBDescription = String(describing: resultB) 236 | } 237 | 238 | message = """ 239 | 240 | --------------------------- 241 | Failed To Assert Equality 242 | --------------------------- 243 | 244 | # Result A 245 | \(resultADescription) 246 | 247 | 248 | # Result B 249 | \(resultBDescription) 250 | 251 | --------------------------- 252 | """ 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Extensions/AsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class AsyncSequenceTests: XCTestCase { 5 | private var sequence: AsyncStream! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = AsyncStream { continuation in 11 | continuation.yield(1) 12 | continuation.yield(2) 13 | continuation.yield(3) 14 | continuation.finish() 15 | } 16 | } 17 | 18 | // MARK: Assign 19 | 20 | private var assignableValue: Int = 0 21 | 22 | func testAssign() async { 23 | self.sequence.assign(to: \.assignableValue, on: self) 24 | await XCTAssertEventuallyEqual(self.assignableValue, 3) 25 | } 26 | 27 | // MARK: First 28 | 29 | func testFirst() async { 30 | let element = await self.sequence.first() 31 | XCTAssertEqual(element, 1) 32 | } 33 | 34 | // MARK: Last 35 | 36 | func testLast() async { 37 | let element = await self.sequence.last() 38 | XCTAssertEqual(element, 3) 39 | } 40 | 41 | // MARK: Collect 42 | 43 | func testCollect() async { 44 | let values = await self.sequence.collect() 45 | XCTAssertEqual(values, [1, 2, 3]) 46 | } 47 | 48 | func testCollectWithLimit() async { 49 | let values = await self.sequence.collect(2) 50 | XCTAssertEqual(values, [1, 2]) 51 | } 52 | 53 | // MARK: Sink 54 | 55 | func testSink() async { 56 | let store = Store() 57 | self.sequence.sink { await store.append($0) } 58 | 59 | await XCTAssertEventuallyEqual( 60 | { await store.values }, 61 | { [1, 2, 3] } 62 | ) 63 | } 64 | 65 | func testSinkWithFinishedCompletion() async { 66 | let completionExpectation = self.expectation(description: "Completion called") 67 | let store = Store() 68 | self.sequence.sink( 69 | receiveValue: { await store.append($0) }, 70 | receiveCompletion: { 71 | switch $0 { 72 | case .failure(let error): 73 | XCTFail("Invalid completion case: Failure \(error)") 74 | case .finished: 75 | completionExpectation.fulfill() 76 | } 77 | } 78 | ) 79 | 80 | await self.waitForExpectations(timeout: 5.0, handler: nil) 81 | 82 | let values = await store.values 83 | XCTAssertEqual(values, [1, 2, 3]) 84 | } 85 | 86 | func testSinkWithFailedCompletion() async { 87 | let completionExpectation = self.expectation(description: "Completion called") 88 | let sequence = AsyncThrowingStream { continuation in 89 | continuation.yield(1) 90 | continuation.yield(2) 91 | continuation.yield(3) 92 | continuation.finish(throwing: TestError()) 93 | } 94 | 95 | let store = Store() 96 | sequence.sink( 97 | receiveValue: { await store.append($0) }, 98 | receiveCompletion: { 99 | switch $0 { 100 | case .failure: 101 | completionExpectation.fulfill() 102 | case .finished: 103 | XCTFail("Invalid completion case: Finished") 104 | } 105 | } 106 | ) 107 | 108 | await self.waitForExpectations(timeout: 5.0, handler: nil) 109 | 110 | let values = await store.values 111 | XCTAssertEqual(values, [1, 2, 3]) 112 | } 113 | 114 | func testSinkWithCancellation() async { 115 | let completionExpectation = self.expectation(description: "Completion called") 116 | let sequence = AsyncThrowingStream { continuation in 117 | continuation.yield(1) 118 | } 119 | 120 | let store = Store() 121 | let task = sequence.sink( 122 | receiveValue: { await store.append($0) }, 123 | receiveCompletion: { 124 | switch $0 { 125 | case .failure(let error) where error is CancellationError: 126 | completionExpectation.fulfill() 127 | case .failure(let error): 128 | XCTFail("Invalid failure error: \(error)") 129 | case .finished: 130 | XCTFail("Invalid completion case: Finished") 131 | } 132 | } 133 | ) 134 | 135 | task.cancel() 136 | await self.waitForExpectations(timeout: 5.0, handler: nil) 137 | 138 | let values = await store.values 139 | XCTAssertEqual(values, [1]) 140 | } 141 | } 142 | 143 | // MARK: Store 144 | 145 | fileprivate actor Store { 146 | var values: [T] = [] 147 | 148 | fileprivate func append(_ newElement: T) { 149 | self.values.append(newElement) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Extensions/TimeIntervalTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class TimeIntervalTests: XCTestCase { 5 | func testAsNanoseconds() { 6 | XCTAssertEqual(1.asNanoseconds, 1_000_000_000) 7 | XCTAssertEqual(1.5.asNanoseconds, 1_500_000_000) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/AnyAsyncSequenceableTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class AnyAsyncSequenceableTests: XCTestCase { 5 | func testErasingJust() async throws { 6 | let values = await Just(1) 7 | .eraseToAnyAsyncSequenceable() 8 | .collect() 9 | XCTAssertEqual(values, [1]) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/AnyThrowingAsyncSequenceableTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class AnyThrowingAsyncSequenceableTests: XCTestCase { 5 | func testErasingFail() async throws { 6 | await XCTAsyncAssertThrow { 7 | _ = try await Fail(error: .init()) 8 | .eraseToAnyThrowingAsyncSequenceable() 9 | .collect() 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/CatchErrorAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | class CatchErrorTests: XCTestCase { 5 | func testErrorCaught() async { 6 | let replacement = 0 7 | 8 | let values = await Fail( 9 | error: TestError() 10 | ) 11 | .catch { _ in 12 | Just(replacement) 13 | } 14 | .collect() 15 | 16 | XCTAssertEqual(values, [replacement]) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/ChainAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class ChainAsyncSequenceTests: XCTestCase { 5 | private var sequenceA: SequenceAsyncSequence<[Int]>! 6 | private var sequenceB: SequenceAsyncSequence<[Int]>! 7 | private var sequenceC: SequenceAsyncSequence<[Int]>! 8 | 9 | // MARK: Setup 10 | 11 | override func setUpWithError() throws { 12 | self.sequenceA = [1, 2, 3].async 13 | self.sequenceB = [4, 5, 6].async 14 | self.sequenceC = [7, 8, 9].async 15 | } 16 | 17 | // MARK: Tests 18 | 19 | func testChainingTwoSequences() async { 20 | let values = await self.sequenceA.chain(with: self.sequenceB).collect() 21 | XCTAssertEqual(values, [1, 2, 3, 4, 5, 6]) 22 | } 23 | 24 | func testChainingThreeSequences() async { 25 | let values = await self.sequenceA 26 | .chain(with: self.sequenceB) 27 | .chain(with: self.sequenceC) 28 | .collect() 29 | 30 | XCTAssertEqual(values, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/CombineLatest3AsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class CombineLatest3AsyncSequenceTests: XCTestCase { 5 | private var sequenceA: AnyAsyncSequenceable! 6 | private var sequenceB: AnyAsyncSequenceable! 7 | private var sequenceC: AnyAsyncSequenceable! 8 | 9 | // MARK: Setup 10 | 11 | override func setUpWithError() throws { 12 | self.sequenceA = [1, 2, 3, 4].async.eraseToAnyAsyncSequenceable() 13 | self.sequenceB = [5, 6, 7, 8, 9].async.eraseToAnyAsyncSequenceable() 14 | self.sequenceC = [10, 11].async.eraseToAnyAsyncSequenceable() 15 | } 16 | 17 | // MARK: Tests 18 | 19 | func testCombiningTwoSequences() async { 20 | let values = await self 21 | .sequenceA 22 | .combineLatest(self.sequenceB, self.sequenceC) 23 | .collect() 24 | 25 | XCTAssertEqual(values.count, 5) 26 | XCTAssertEqual(values[0].0, 1) 27 | XCTAssertEqual(values[0].1, 5) 28 | XCTAssertEqual(values[0].2, 10) 29 | 30 | XCTAssertEqual(values[1].0, 2) 31 | XCTAssertEqual(values[1].1, 6) 32 | XCTAssertEqual(values[1].2, 11) 33 | 34 | XCTAssertEqual(values[2].0, 3) 35 | XCTAssertEqual(values[2].1, 7) 36 | XCTAssertEqual(values[2].2, 11) 37 | 38 | XCTAssertEqual(values[3].0, 4) 39 | XCTAssertEqual(values[3].1, 8) 40 | XCTAssertEqual(values[3].2, 11) 41 | 42 | XCTAssertEqual(values[4].0, 4) 43 | XCTAssertEqual(values[4].1, 9) 44 | XCTAssertEqual(values[4].2, 11) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/CombineLatestAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class CombineLatestAsyncSequenceTests: XCTestCase { 5 | private var sequenceA: AnyAsyncSequenceable! 6 | private var sequenceB: AnyAsyncSequenceable! 7 | 8 | // MARK: Setup 9 | 10 | override func setUpWithError() throws { 11 | self.sequenceA = [1, 2, 3, 4].async.eraseToAnyAsyncSequenceable() 12 | self.sequenceB = [5, 6, 7, 8, 9].async.eraseToAnyAsyncSequenceable() 13 | } 14 | 15 | // MARK: Tests 16 | 17 | func testCombiningTwoSequences() async { 18 | let values = await self 19 | .sequenceA 20 | .combineLatest(self.sequenceB) 21 | .collect() 22 | 23 | XCTAssertEqual(values.count, 5) 24 | XCTAssertEqual(values[0].0, 1) 25 | XCTAssertEqual(values[0].1, 5) 26 | 27 | XCTAssertEqual(values[1].0, 2) 28 | XCTAssertEqual(values[1].1, 6) 29 | 30 | XCTAssertEqual(values[2].0, 3) 31 | XCTAssertEqual(values[2].1, 7) 32 | 33 | XCTAssertEqual(values[3].0, 4) 34 | XCTAssertEqual(values[3].1, 8) 35 | 36 | XCTAssertEqual(values[4].0, 4) 37 | XCTAssertEqual(values[4].1, 9) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/CurrentElementAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class CurrentElementAsyncSequenceTests: XCTestCase { 5 | private var sequence: CurrentElementAsyncSequence! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = CurrentElementAsyncSequence(0) 11 | } 12 | 13 | // MARK: Tests 14 | 15 | func testCurrentElement() async { 16 | var element = await self.sequence.element 17 | XCTAssertEqual(element, 0) 18 | 19 | await self.sequence.yield(1) 20 | element = await self.sequence.element 21 | XCTAssertEqual(element, 1) 22 | 23 | await self.sequence.yield(2) 24 | await self.sequence.yield(3) 25 | await self.sequence.yield(4) 26 | element = await self.sequence.element 27 | XCTAssertEqual(element, 4) 28 | } 29 | 30 | func testSequence() async { 31 | await self.sequence.yield(1) 32 | await self.sequence.finish(with: 2) 33 | 34 | let values = await self.sequence.collect() 35 | XCTAssertEqual(values, [0, 1, 2]) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/DebounceAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class DebounceAsyncSequenceTests: XCTestCase { 5 | 6 | func testDebounce() async { 7 | let stream = AsyncStream { continuation in 8 | continuation.yield(0) 9 | try? await Task.sleep(seconds: 0.4) 10 | continuation.yield(1) 11 | continuation.yield(2) 12 | try? await Task.sleep(seconds: 0.4) 13 | continuation.yield(3) 14 | try? await Task.sleep(seconds: 0.4) 15 | continuation.finish() 16 | } 17 | 18 | let values = await stream 19 | .debounce(for: 0.1) 20 | .collect() 21 | 22 | XCTAssertEqual(values, [0, 2, 3]) 23 | } 24 | 25 | func testDebounceWithNoValues() async { 26 | let values = await AsyncStream { 27 | $0.finish() 28 | } 29 | .debounce(for: 0.3) 30 | .collect() 31 | 32 | XCTAssert(values.isEmpty) 33 | } 34 | 35 | func testWithSequenceInstantFinish() async { 36 | let values = await Just(0) 37 | .debounce(for: 0.3) 38 | .collect() 39 | 40 | XCTAssert(values.isEmpty) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/DelayAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class DelayAsyncSequenceTests: XCTestCase { 5 | private var sequence: AnyAsyncSequenceable! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = [0, 1, 2].async.eraseToAnyAsyncSequenceable() 11 | } 12 | 13 | // MARK: Tests 14 | 15 | func testDelay() async throws { 16 | var values: [TimestampedValue] = [] 17 | 18 | for try await value in self.sequence.delay(for: 0.5) { 19 | values.append(.init(value: value)) 20 | } 21 | 22 | var difference = values[1].timestamp.timeIntervalSince(values[0].timestamp) 23 | XCTAssert(difference >= 0.5) 24 | 25 | difference = values[2].timestamp.timeIntervalSince(values[1].timestamp) 26 | XCTAssert(difference >= 0.5) 27 | 28 | XCTAssertEqual(values.map(\.value), [0, 1, 2]) 29 | } 30 | } 31 | 32 | 33 | // MARK: OutputValue 34 | 35 | fileprivate struct TimestampedValue { 36 | var value: T 37 | var timestamp = Date() 38 | } 39 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/EmptyTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class EmptyTests: XCTestCase { 5 | func testNothingEmitted() async { 6 | let values = await Empty().collect() 7 | XCTAssertEqual(values, []) 8 | } 9 | 10 | func testSequenceDoesntComplete() async { 11 | do { 12 | try await withThrowingTaskGroup(of: Void.self) { group in 13 | let timeout: TimeInterval = 1 14 | let deadline = Date(timeIntervalSinceNow: timeout) 15 | 16 | group.addTask { 17 | _ = await Empty(completeImmediately: false).collect() 18 | throw TaskCompletedError() 19 | } 20 | 21 | group.addTask { 22 | if deadline.timeIntervalSinceNow > 0 { 23 | try await Task.sleep(seconds: timeout) 24 | } 25 | } 26 | 27 | try await group.next() 28 | group.cancelAll() 29 | } 30 | 31 | // All good! 32 | } catch _ as TaskCompletedError { 33 | XCTFail("Task incorrectly finished") 34 | } catch { 35 | XCTFail("Unknown error thrown \(error)") 36 | } 37 | } 38 | } 39 | 40 | 41 | 42 | // MARK: TaskCompletedError 43 | 44 | fileprivate struct TaskCompletedError: Error { } 45 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/FailTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class FailTests: XCTestCase { 5 | func testErrorThrown() async { 6 | await XCTAsyncAssertThrow { 7 | _ = try await Fail( 8 | error: TestError() 9 | ).collect() 10 | } 11 | } 12 | 13 | func testErrorOnlyThrownOnce() async { 14 | let replacement = 0 15 | let values = await Fail( 16 | error: TestError() 17 | ) 18 | .replaceError(with: replacement) 19 | .collect() 20 | 21 | XCTAssertEqual(values, [0]) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/JustTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class JustTests: XCTestCase { 5 | func testEmittedElements() async { 6 | let values = await Just(1).collect() 7 | XCTAssertEqual(values, [1]) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/Merge3AsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class Merge3AsyncSequenceTests: XCTestCase { 5 | private var sequenceA: AnyAsyncSequenceable! 6 | private var sequenceB: AnyAsyncSequenceable! 7 | private var sequenceC: AnyAsyncSequenceable! 8 | 9 | // MARK: Setup 10 | 11 | override func setUpWithError() throws { 12 | self.sequenceA = [1, 4].async.eraseToAnyAsyncSequenceable() 13 | self.sequenceB = [2, 5, 7].async.eraseToAnyAsyncSequenceable() 14 | self.sequenceC = [3, 6].async.eraseToAnyAsyncSequenceable() 15 | } 16 | 17 | // MARK: Tests 18 | 19 | func testMergingThreeSequences() async throws { 20 | let values = await self 21 | .sequenceA 22 | .merge(with: self.sequenceB, self.sequenceC) 23 | .collect() 24 | 25 | XCTAssertEqual(values.count, 7) 26 | XCTAssert(values.contains(1)) 27 | XCTAssert(values.contains(2)) 28 | XCTAssert(values.contains(3)) 29 | XCTAssert(values.contains(4)) 30 | XCTAssert(values.contains(5)) 31 | XCTAssert(values.contains(6)) 32 | XCTAssert(values.contains(7)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/MergeAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class MergeAsyncSequenceTests: XCTestCase { 5 | private var sequenceA: AnyAsyncSequenceable! 6 | private var sequenceB: AnyAsyncSequenceable! 7 | 8 | // MARK: Setup 9 | 10 | override func setUpWithError() throws { 11 | self.sequenceA = [1, 2, 3, 4].async.eraseToAnyAsyncSequenceable() 12 | self.sequenceB = [5, 6, 7, 8, 9].async.eraseToAnyAsyncSequenceable() 13 | } 14 | 15 | // MARK: Tests 16 | 17 | func testMergingTwoSequences() async { 18 | let values = await self 19 | .sequenceA 20 | .merge(with: self.sequenceB) 21 | .collect() 22 | 23 | XCTAssertEqual(values.count, 9) 24 | XCTAssert(values.contains(1)) 25 | XCTAssert(values.contains(2)) 26 | XCTAssert(values.contains(3)) 27 | XCTAssert(values.contains(4)) 28 | XCTAssert(values.contains(5)) 29 | XCTAssert(values.contains(6)) 30 | XCTAssert(values.contains(7)) 31 | XCTAssert(values.contains(8)) 32 | XCTAssert(values.contains(9)) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/NotificationCenterAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class NotificationCenterAsyncSequenceTests: XCTestCase { 5 | private var task: Task! 6 | private var notifications: [Notification] = [] 7 | 8 | override func setUp() async throws { 9 | self.task = NotificationCenter.default 10 | .sequence(for: .testNotification) 11 | .sink( 12 | receiveValue: { [weak self] in self?.notifications.append($0) } 13 | ) 14 | } 15 | 16 | func testNotificationsAreReceived() async throws { 17 | NotificationCenter.default.post(name: .testNotification, object: 1) 18 | NotificationCenter.default.post(name: .testNotification, object: 2) 19 | await XCTAssertEventuallyEqual( 20 | self.notifications.map { $0.object as? Int }, 21 | [1, 2] 22 | ) 23 | } 24 | } 25 | 26 | // MARK: Notification name 27 | 28 | private extension Notification.Name { 29 | static let testNotification = Notification.Name("testNotification") 30 | } 31 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/PassthroughAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class PassthroughAsyncSequenceTests: XCTestCase { 5 | private var sequence: PassthroughAsyncSequence! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = .init() 11 | } 12 | 13 | // MARK: Tests 14 | 15 | func testSequence() async throws { 16 | self.sequence.yield(0) 17 | self.sequence.yield(1) 18 | self.sequence.finish(with: 2) 19 | 20 | let values = await self.sequence.collect() 21 | XCTAssertEqual(values, [0, 1, 2]) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/RemoveDuplicatesAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class RemoveDuplicatesAsyncSequenceTests: XCTestCase { 5 | private var sequence: AnyAsyncSequenceable! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = [1, 1, 2, 2, 3, 3, 1].async.eraseToAnyAsyncSequenceable() 11 | } 12 | 13 | // MARK: Tests 14 | 15 | func testDuplicatesRemoved() async { 16 | let values = await self 17 | .sequence 18 | .removeDuplicates() 19 | .collect() 20 | 21 | XCTAssertEqual(values, [1, 2, 3, 1]) 22 | } 23 | 24 | func testRemovingDuplicatesWithPredicate() async { 25 | let values = await self 26 | .sequence 27 | .removeDuplicates { previous, current in 28 | previous >= current 29 | } 30 | .collect() 31 | 32 | XCTAssertEqual(values, [1, 2, 3]) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/ReplaceErrorAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class ReplaceErrorTests: XCTestCase { 5 | func testErrorReplaced() async { 6 | let replacement = 0 7 | 8 | let values = await Fail( 9 | error: TestError() 10 | ) 11 | .replaceError(with: replacement) 12 | .collect() 13 | 14 | XCTAssertEqual(values, [replacement]) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/SequenceAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class SequenceAsyncSequenceTests: XCTestCase { 5 | func testCreatingAsyncSequence() async { 6 | let array = [0, 1, 2, 3] 7 | let sequence = array.async 8 | let values = await sequence.collect() 9 | 10 | XCTAssertEqual(values, array) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/SharedAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class SharedAsyncSequenceTests: XCTestCase { 5 | private var sequence: SharedAsyncSequence>! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = PassthroughAsyncSequence().shared() 11 | } 12 | 13 | // MARK: Tests 14 | 15 | func testSharedStreamShouldNotThrowExceptionAndReceiveAllValues() async { 16 | let taskCompleteExpectation = self.expectation(description: "Task complete") 17 | Task { 18 | let values = try await self.sequence.collect() 19 | XCTAssertEqual(values, [0, 1, 2, 3]) 20 | taskCompleteExpectation.fulfill() 21 | } 22 | 23 | let detachedTaskCompleteExpectation = self.expectation(description: "Detached task complete") 24 | Task.detached { 25 | let values = try await self.sequence.collect() 26 | XCTAssertEqual(values, [0, 1, 2, 3]) 27 | detachedTaskCompleteExpectation.fulfill() 28 | } 29 | 30 | Task.detached { 31 | try? await Task.sleep(seconds: 0.5) 32 | self.sequence.yield(0) 33 | self.sequence.yield(1) 34 | self.sequence.yield(2) 35 | self.sequence.finish(with: 3) 36 | } 37 | 38 | await self.waitForExpectations(timeout: 5) 39 | } 40 | 41 | func testAccessingBaseCurrentElementAsyncSequenceFunctionality() async throws { 42 | let valueA = 0 43 | let valueB = 1 44 | let valueC = 2 45 | 46 | let sequence = CurrentElementAsyncSequence(valueA).shared() 47 | 48 | // Yield new value 49 | await sequence.yield(valueB) 50 | await sequence.finish(with: valueC) 51 | 52 | let values = try await sequence.collect() 53 | XCTAssertEqual(values, [0, 1, 2]) 54 | 55 | let currentValue = await sequence.element() 56 | XCTAssertEqual(currentValue, valueC) 57 | } 58 | 59 | func testAccessingBasePassthroughAsyncSequenceFunctionality() async throws { 60 | let valueA = 0 61 | let valueB = 1 62 | 63 | let sequence = PassthroughAsyncSequence().shared() 64 | 65 | // Yield new value 66 | sequence.yield(valueA) 67 | sequence.finish(with: valueB) 68 | 69 | let values = try await sequence.collect() 70 | XCTAssertEqual(values, [0, 1]) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/ThrottleAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class ThrottleAsyncSequenceTests: XCTestCase { 5 | private var sequence: AsyncStream! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = AsyncStream { continuation in 11 | continuation.yield(0) 12 | try? await Task.sleep(nanoseconds: 100_000_000) 13 | continuation.yield(1) 14 | try? await Task.sleep(nanoseconds: 100_000_000) 15 | continuation.yield(2) 16 | continuation.yield(3) 17 | continuation.yield(4) 18 | continuation.yield(5) 19 | continuation.finish() 20 | } 21 | } 22 | 23 | // MARK: Tests 24 | 25 | func testThrottle() async throws { 26 | let values = await self.sequence 27 | .throttle(for: 0.05, latest: false) 28 | .collect() 29 | 30 | XCTAssertEqual(values, [0, 1, 2]) 31 | } 32 | 33 | func testThrottleLatest() async throws { 34 | let values = await self.sequence 35 | .throttle(for: 0.05, latest: true) 36 | .collect() 37 | 38 | XCTAssertEqual(values, [0, 1, 2]) 39 | } 40 | 41 | func testThrottleWithNoValues() async throws { 42 | let values = await AsyncStream { 43 | $0.finish() 44 | } 45 | .throttle(for: 0.05, latest: true) 46 | .collect() 47 | 48 | XCTAssert(values.isEmpty) 49 | } 50 | 51 | func testThrottleWithOneValue() async throws { 52 | let values = await Just(0) 53 | .throttle(for: 0.05, latest: true) 54 | .collect() 55 | 56 | XCTAssertEqual(values, [0]) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/ThrowingPassthroughAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class ThrowingPassthroughAsyncSequenceTests: XCTestCase { 5 | private var sequence: ThrowingPassthroughAsyncSequence! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = .init() 11 | } 12 | 13 | // MARK: Tests 14 | 15 | func testSequence() async throws { 16 | self.sequence.yield(0) 17 | self.sequence.yield(1) 18 | self.sequence.finish(with: 2) 19 | 20 | let values = try await self.sequence.collect() 21 | XCTAssertEqual(values, [0, 1, 2]) 22 | } 23 | 24 | func testSequenceThrows() async throws { 25 | self.sequence.finish(throwing: TestError()) 26 | 27 | await XCTAsyncAssertThrow { 28 | try await self.sequence.collect() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/TimerAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class TimerAsyncSequenceTests: XCTestCase { 5 | private var sequence: TimerAsyncSequence! 6 | 7 | // MARK: Setup 8 | 9 | override func setUpWithError() throws { 10 | self.sequence = .init(interval: 0.5) 11 | } 12 | 13 | // MARK: Tests 14 | 15 | func testTimerEmissions() async throws { 16 | var values: [Date] = [] 17 | let start = Date() 18 | var end = Date() 19 | 20 | for await value in self.sequence { 21 | values.append(value) 22 | 23 | if values.count == 3 { 24 | end = Date() 25 | self.sequence.cancel() 26 | } 27 | } 28 | 29 | var difference = end.timeIntervalSince(start) 30 | XCTAssert(difference >= 1.5) 31 | 32 | difference = values[1].timeIntervalSince(values[0]) 33 | XCTAssert(difference >= 0.5) 34 | 35 | difference = values[2].timeIntervalSince(values[1]) 36 | XCTAssert(difference >= 0.5) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/Sequences/ZipAsyncSequenceTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Asynchrone 3 | 4 | final class ZipAsyncSequenceTests: XCTestCase { 5 | private var sequenceA: AnyAsyncSequenceable! 6 | private var sequenceB: AnyAsyncSequenceable! 7 | 8 | // MARK: Setup 9 | 10 | override func setUpWithError() throws { 11 | self.sequenceA = [1, 2].async.eraseToAnyAsyncSequenceable() 12 | self.sequenceB = [5, 6, 7].async.eraseToAnyAsyncSequenceable() 13 | } 14 | 15 | // MARK: Tests 16 | 17 | func testZippingTwoSequences() async { 18 | let values = await self 19 | .sequenceA 20 | .zip(self.sequenceB) 21 | .collect() 22 | 23 | XCTAssertEqual(values.count, 2) 24 | XCTAssertEqual(values[0].0, 1) 25 | XCTAssertEqual(values[0].1, 5) 26 | 27 | XCTAssertEqual(values[1].0, 2) 28 | XCTAssertEqual(values[1].1, 6) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Tests/AsynchroneTests/TestError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct TestError: Error { } 4 | --------------------------------------------------------------------------------