Adds try await myCombineStream.firstValue
, await myCombineStream.firstResult
and await myCombineStream.completed()
for convenient usage because there is no Apple-provided API for it, only to turn a Combine stream into AsyncStream using .values
.
- https://developer.apple.com/documentation/combine/publisher/values-1dm9r
- https://developer.apple.com/documentation/combine/publisher/values-v7nz
You can use JohnSundell/AsyncCompatibilityKit to have .values
backported to iOS 13 and related macOS.
Let's convert a Combine publisher provided by Apple to make a network request into async/await
Combine implementation
URLSession.shared.dataTaskPublisher(for: url).sink { completion in
// check error
} receiveValue: { value in
// process value
}.store(in: &cancellables)
Async/await bridge
import CombineToAsyncAwait
extension URLSession {
func data(for url: URL) async throws -> (Data, URLResponse) {
try await self.dataTaskPublisher(for: url).firstValue
}
}
final class NetworkingTests: XCTestCase {
func testNetworkRequest() async throws {
let (data, response) = try await URLSession.shared.data(for: URL(string: "https://google.com")!)
}
}
Note: Apple has actually introduced async/await versions of this as a part of foundation on iOS 15 and corresponding OSes, so this is only to demonstrate how easy it is to turn a Combine publisher into an async
method.
One of the disadvantages of async/await is that the error gets erased, you can use firstResult
instead that returns a Result
type where the error type matches the combine publisher.
let result = await myApiProvider.userProfile(id: 123).firstResult
switch result {
case .success(let user):
// ...
case .failure(let fetchUserError):
switch fetchUserError {
case .notFound:
// ...
}
}
For streams of void
value, you may want to await stream completion instead of a value.
See this package and thousands of others on swiftpack.co