Skip to content

Commit

Permalink
Allow creating a Single from async closure
Browse files Browse the repository at this point in the history
  • Loading branch information
freak4pc committed Oct 4, 2024
1 parent 3c68f22 commit 662d91b
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 1 deletion.
14 changes: 14 additions & 0 deletions Documentation/SwiftConcurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,17 @@ stream.asObservable()
onError: { ... }
)
```

### Wrapping an `async` result as a `Single`

If you already have an async piece of work that returns a single result you wish to await, you can bridge it back to the Rx wordl by using `Single.create`, a special overload which takes an `async throws` closure where you can simply await your async work:

```swift
func doIncredibleWork() async throws -> AmazingRespones {
...
}

let single = Single.create {
try await doIncredibleWork()
} // Single<AmazingResponse>
```
2 changes: 1 addition & 1 deletion Rx.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,7 @@
C8093CBF1B8A72BE0088E94D /* PublishSubject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PublishSubject.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
C8093CC01B8A72BE0088E94D /* ReplaySubject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ReplaySubject.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
C8093CC11B8A72BE0088E94D /* SubjectType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubjectType.swift; sourceTree = "<group>"; };
C8093E8B1B8A732E0088E94D /* DelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DelegateProxy.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
C8093E8B1B8A732E0088E94D /* DelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DelegateProxy.swift; sourceTree = "<group>"; };
C8093E8C1B8A732E0088E94D /* DelegateProxyType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelegateProxyType.swift; sourceTree = "<group>"; };
C8093E9C1B8A732E0088E94D /* RxTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTarget.swift; sourceTree = "<group>"; };
C8093E9D1B8A732E0088E94D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,38 @@ import Foundation
#if swift(>=5.6) && canImport(_Concurrency) && !os(Linux)
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public extension PrimitiveSequenceType where Trait == SingleTrait {
/**
Creates an `Single` from the result of an asynchronous operation

- seealso: [create operator on reactivex.io](http://reactivex.io/documentation/operators/create.html)

- parameter work: An `async` closure expected to return an element of type `Element`

- returns: A `Single` of the `async` closure's element type
*/
@_disfavoredOverload
static func create(
detached: Bool = false,
priority: TaskPriority? = nil,
work: @Sendable @escaping () async throws -> Element
) -> PrimitiveSequence<Trait, Element> {
.create { single in
let operation: () async throws -> Void = {
await single(
Result { try await work() }
)
}

let task = if detached {
Task.detached(priority: priority, operation: operation)

Check warning on line 37 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (Unix)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 37 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (Unix)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 37 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (Unix)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 37 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (iOS-Example)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 37 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (tvOS)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 37 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (iOS)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 37 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (watchOS)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races
} else {
Task(priority: priority, operation: operation)

Check warning on line 39 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (Unix)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 39 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (Unix)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 39 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (Unix)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 39 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (iOS-Example)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 39 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (tvOS)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 39 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (iOS)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races

Check warning on line 39 in RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

View workflow job for this annotation

GitHub Actions / Xcode 15 (watchOS)

converting non-sendable function value to '@sendable () async throws -> Void' may introduce data races
}

return Disposables.create { task.cancel() }
}
}

/// Allows awaiting the success or failure of this `Single`
/// asynchronously via Swift's concurrency features (`async/await`)
///
Expand Down Expand Up @@ -161,4 +193,16 @@ public extension PrimitiveSequenceType where Trait == CompletableTrait, Element
}
}
}

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
extension Result where Failure == Swift.Error {
@_disfavoredOverload
init(catching body: () async throws -> Success) async {
do {
self = try await .success(body())
} catch {
self = .failure(error)
}
}
}
#endif
14 changes: 14 additions & 0 deletions Tests/RxSwiftTests/PrimitiveSequence+ConcurrencyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Dispatch
import RxSwift
import XCTest
import RxTest
import RxBlocking

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
class PrimitiveSequenceConcurrencyTests: RxTest {
Expand Down Expand Up @@ -72,6 +73,19 @@ extension PrimitiveSequenceConcurrencyTests {
task.cancel()
}

func testCreateSingleFromAsync() {
let randomResult = Int.random(in: 100...100000)
let work: () async throws -> Int = { randomResult }

let single = Single.create {
try await work()
}

XCTAssertEqual(
try! single.toBlocking().toArray(),
[randomResult]
)
}
}

// MARK: - Maybe
Expand Down

0 comments on commit 662d91b

Please sign in to comment.