Skip to content

Commit 662d91b

Browse files
committed
Allow creating a Single from async closure
1 parent 3c68f22 commit 662d91b

File tree

4 files changed

+73
-1
lines changed

4 files changed

+73
-1
lines changed

Documentation/SwiftConcurrency.md

+14
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,17 @@ stream.asObservable()
6363
onError: { ... }
6464
)
6565
```
66+
67+
### Wrapping an `async` result as a `Single`
68+
69+
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:
70+
71+
```swift
72+
func doIncredibleWork() async throws -> AmazingRespones {
73+
...
74+
}
75+
76+
let single = Single.create {
77+
try await doIncredibleWork()
78+
} // Single<AmazingResponse>
79+
```

Rx.xcodeproj/project.pbxproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1071,7 +1071,7 @@
10711071
C8093CBF1B8A72BE0088E94D /* PublishSubject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = PublishSubject.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
10721072
C8093CC01B8A72BE0088E94D /* ReplaySubject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = ReplaySubject.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
10731073
C8093CC11B8A72BE0088E94D /* SubjectType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubjectType.swift; sourceTree = "<group>"; };
1074-
C8093E8B1B8A732E0088E94D /* DelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DelegateProxy.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
1074+
C8093E8B1B8A732E0088E94D /* DelegateProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = DelegateProxy.swift; sourceTree = "<group>"; };
10751075
C8093E8C1B8A732E0088E94D /* DelegateProxyType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DelegateProxyType.swift; sourceTree = "<group>"; };
10761076
C8093E9C1B8A732E0088E94D /* RxTarget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxTarget.swift; sourceTree = "<group>"; };
10771077
C8093E9D1B8A732E0088E94D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };

RxSwift/Traits/PrimitiveSequence/PrimitiveSequence+Concurrency.swift

+44
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,38 @@ import Foundation
1111
#if swift(>=5.6) && canImport(_Concurrency) && !os(Linux)
1212
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
1313
public extension PrimitiveSequenceType where Trait == SingleTrait {
14+
/**
15+
Creates an `Single` from the result of an asynchronous operation
16+
17+
- seealso: [create operator on reactivex.io](http://reactivex.io/documentation/operators/create.html)
18+
19+
- parameter work: An `async` closure expected to return an element of type `Element`
20+
21+
- returns: A `Single` of the `async` closure's element type
22+
*/
23+
@_disfavoredOverload
24+
static func create(
25+
detached: Bool = false,
26+
priority: TaskPriority? = nil,
27+
work: @Sendable @escaping () async throws -> Element
28+
) -> PrimitiveSequence<Trait, Element> {
29+
.create { single in
30+
let operation: () async throws -> Void = {
31+
await single(
32+
Result { try await work() }
33+
)
34+
}
35+
36+
let task = if detached {
37+
Task.detached(priority: priority, operation: operation)
38+
} else {
39+
Task(priority: priority, operation: operation)
40+
}
41+
42+
return Disposables.create { task.cancel() }
43+
}
44+
}
45+
1446
/// Allows awaiting the success or failure of this `Single`
1547
/// asynchronously via Swift's concurrency features (`async/await`)
1648
///
@@ -161,4 +193,16 @@ public extension PrimitiveSequenceType where Trait == CompletableTrait, Element
161193
}
162194
}
163195
}
196+
197+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
198+
extension Result where Failure == Swift.Error {
199+
@_disfavoredOverload
200+
init(catching body: () async throws -> Success) async {
201+
do {
202+
self = try await .success(body())
203+
} catch {
204+
self = .failure(error)
205+
}
206+
}
207+
}
164208
#endif

Tests/RxSwiftTests/PrimitiveSequence+ConcurrencyTests.swift

+14
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Dispatch
1111
import RxSwift
1212
import XCTest
1313
import RxTest
14+
import RxBlocking
1415

1516
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
1617
class PrimitiveSequenceConcurrencyTests: RxTest {
@@ -72,6 +73,19 @@ extension PrimitiveSequenceConcurrencyTests {
7273
task.cancel()
7374
}
7475

76+
func testCreateSingleFromAsync() {
77+
let randomResult = Int.random(in: 100...100000)
78+
let work: () async throws -> Int = { randomResult }
79+
80+
let single = Single.create {
81+
try await work()
82+
}
83+
84+
XCTAssertEqual(
85+
try! single.toBlocking().toArray(),
86+
[randomResult]
87+
)
88+
}
7589
}
7690

7791
// MARK: - Maybe

0 commit comments

Comments
 (0)