Logo for tanaschita.com

How to bridge async/await functions to Combine's Future type in Swift

Learn how to call async/await code within Combine based APIs.

22 Aug 2022 · 2 min read

When working with asynchronious code in Swift, we might have to find ways to mix and connect different asynchronious patterns like using the Combine framework together with Swift's async/await API.

In this article, we'll look at how to call async-marked functions within Combine based APIs.

Sponsorship logo
Using Proxyman to inspect network traffic
Proxyman is a native debugging proxy that can act as a man-in-the-middle between your application and web server. You can use its powerful toolkit to inspect network calls and debug your application on Mac, iOS Simulator, or remote devices effortlessly.
CLICK TO LEARN MORE

Let's look at the following async function which loads a user from server with the async/await pattern.

func loadUser() async throws -> User {
// Load user from server
return user // or throw error
}

To implement the same behaviour with Combine, its Future publisher comes in handy. A future is initialized with a closure that takes a Future.Promise. After doing some asynchronous work, we call that closure with a Result that is either a success or a failure. Combine then automatically maps the result into proper publisher events.

Let's look at how the async function above could be implemented with a Future publisher:

func loadUser() -> Future<User, Error> {
return Future() { promise in
// Load user from server.
promise(.success(user)) // or promise(.failure(error))
}
}

But since we already have an implementation that loads a user, we'd like to avoid duplication writing the same logic twice.

So let's try to find a more generic solution, that allows us to convert an async func to a Future publisher.

extension Future where Failure == Error {
convenience init(asyncFunc: @escaping () async throws -> Output) {
self.init { promise in
Task {
do {
let result = try await asyncFunc()
promise(.success(result))
} catch {
promise(.failure(error))
}
}
}
}
}

In the code above, we extend the Future type with a convenience initializer that allows us to initialize an instance with an async closure. Applied to our example:

func loadUser() -> Future<Int, Error> {
return Future(asyncFunc: {
try await loadUser()
})
}

For non-throwing async functions, we could add another extension:

extension Future where Failure == Never {
convenience init(asyncFunc: @escaping () async -> Output) {
self.init { promise in
Task {
let result = await asyncFunc()
promise(.success(result))
}
}
}
}

With a generic solution like this, we now have the possibility to use it on any async function to bridge it to Combine's Future type.

Sponsorship logo
Using Proxyman to inspect network traffic
Proxyman is a native debugging proxy that can act as a man-in-the-middle between your application and web server. You can use its powerful toolkit to inspect network calls and debug your application on Mac, iOS Simulator, or remote devices effortlessly.
CLICK TO LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

combine

reactive programming

swift

Memory management in Combine

Learn how to avoid retain cycles when using the Combine framework in Swift.

12 Sep 2022 · 4 min read

Latest articles and tips

© 2022 tanaschita.com

Privacy policy

Impressum