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
Preparing for a technical iOS job interview
Check out my new book on preparing for a technical iOS job interview with over 200 questions & answers. Test your knowledge on iOS topics such as Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, Authentication, Core Data, Concurrency with async/await, Security, Automated Testing and more.
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
Preparing for a technical iOS job interview
Check out my new book on preparing for a technical iOS job interview with over 200 questions & answers. Test your knowledge on iOS topics such as Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, Authentication, Core Data, Concurrency with async/await, Security, Automated Testing and more.
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

Cheat sheet on Combine operators for iOS development

Get an overview on Publisher operators in Combine.

21 Nov 2022 · 5 min read

Latest articles and tips

© 2023 tanaschita.com

Privacy policy

Impressum