Futures and Promises in Combine | Combine Series #1
Futures are a lesser-known but powerful feature of the Combine framework that allow us to convert @escaping closures into Publishers. In this blog, we will explore how to implement Futures, enabling us to seamlessly integrate these Publishers with other Publishers and Subscribers in our application.
To be short and precise what is Future and promises?
Future is a context for a value that might not yet exist. Generally, we use a future to represent the eventual completion or failure of an asynchronous operation.
Promise is the eventual result of a future.
To understand more about Future and Promises please refer the Apple Official Documentation
In this blog, we will cover the following topics:
- Fetching responses using Combine.
- Fetching responses with @escaping closures.
- Converting @escaping closures into Combine Publishers using Future and Promises.
Fetching response using Combine
Using Combine Publisher we will fetch a response as follows:
import Foundation
import SwiftUI
import Combine
class FuturesAndPromisesViewModel: ObservableObject {
@Published var title: String = "new title"
let url = URL(string: "https://www.google.com")!
var cancellables = Set<AnyCancellable>()
init() {
fetch()
}
func fetch() {
getCombinePublisher()
.sink(receiveCompletion: {
_ in
}, receiveValue: { [weak self] returnedValue in
self?.title = returnedValue
}).store(in: &cancellables)
}
func getCombinePublisher() -> AnyPublisher<String, URLError> {
URLSession.shared.dataTaskPublisher(for: url)
.timeout(1, scheduler: DispatchQueue.main)
.map({ _ in
return "New Value"
})
.eraseToAnyPublisher()
}
}
In the above code, dataTaskPublisher is used for publisher and for the same publisher we are subscribing using sink.
Now the same fetch call we will make using @escaping closure.
Fetching responses with @escaping closures
import Foundation
import SwiftUI
import Combine
class FuturesAndPromisesViewModel: ObservableObject {
@Published var title: String = "new title"
let url = URL(string: "https://www.google.com")!
var cancellables = Set<AnyCancellable>()
init() {
fetch()
}
func fetch() {
// getCombinePublisher()
// .sink(receiveCompletion: {
// _ in
// }, receiveValue: { [weak self] returnedValue in
// self?.title = returnedValue
// }).store(in: &cancellables)
getEscapingClosure { [weak self] returnedValue, error in
self?.title = returnedValue
}
}
func getCombinePublisher() -> AnyPublisher<String, URLError> {
URLSession.shared.dataTaskPublisher(for: url)
.timeout(1, scheduler: DispatchQueue.main)
.map({ _ in
return "New Value"
})
.eraseToAnyPublisher()
}
func getEscapingClosure(completionHandler: @escaping (_ value: String, _ error: Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
completionHandler("New Value 2", nil)
}).resume()
}
}
Converting @escaping closures into Combine Publishers using Future and Promises.
We will add a new function which returns a Future using the above func getEscapingClosure
func getFuturePublisher() -> Future<String, Error> {
return Future { promise in
self.getEscapingClosure { returnedValue, error in
if let error = error {
promise(.failure(error))
} else {
promise(.success(returnedValue))
}
}
}
}
The above code returns a Future using func init(_ attemptToFulfill: @escaping (@escaping Future<Output, Failure>.Promise) -> Void) and attemptToFulfill takes @escaping closure so we make a call to getEscapingClosure which gives a promise of failure and success.
Now we converted the @escaping closure i.e
{ data, response, error in
completionHandler("New Value 2", nil)
}
into Future Publisher and now we can subscribe it as below
getFuturePublisher()
.sink(receiveCompletion: {
_ in
}, receiveValue: { [weak self] returnedValue in
self?.title = returnedValue
}).store(in: &cancellables)
And the complete converted @escaping closure to Future Publisher is as follows:
import Foundation
import SwiftUI
import Combine
class FuturesAndPromisesViewModel: ObservableObject {
@Published var title: String = "new title"
let url = URL(string: "https://www.google.com")!
var cancellables = Set<AnyCancellable>()
init() {
fetch()
}
func fetch() {
// getCombinePublisher()
getFuturePublisher()
.sink(receiveCompletion: {
_ in
}, receiveValue: { [weak self] returnedValue in
self?.title = returnedValue
}).store(in: &cancellables)
// getEscapingClosure { [weak self] returnedValue, error in
// self?.title = returnedValue
// }
}
func getCombinePublisher() -> AnyPublisher<String, URLError> {
URLSession.shared.dataTaskPublisher(for: url)
.timeout(1, scheduler: DispatchQueue.main)
.map({ _ in
return "New Value"
})
.eraseToAnyPublisher()
}
func getEscapingClosure(completionHandler: @escaping (_ value: String, _ error: Error?) -> ()) {
URLSession.shared.dataTask(with: url, completionHandler: { data, response, error in
completionHandler("New Value 2", nil)
}).resume()
}
func getFuturePublisher() -> Future<String, Error> {
return Future { promise in
self.getEscapingClosure { returnedValue, error in
if let error = error {
promise(.failure(error))
} else {
promise(.success(returnedValue))
}
}
}
}
}
So this is how we convert closure to Future Publisher which you can subscribe and use more combine features like combineLatest.
Complete Source Code https://github.com/AnupKevins/FutureAndPromiseCombine