on
Functional Reactive Programming - Future and Single
While Publisher
and Observable
are designed to deliver an infinite stream of elements over time, Combine and RxSwift provides with variations of these protocols to model any cases where we only need to retrieve a single element or an error.
In these cases, the emitter produces one value (or an error) and immediately terminates.
— Combine
A Future
is a publisher that eventually produces one value and then finishes or fails.
final public class Future<Output, Failure> : Publisher where Failure : Error {
public typealias Promise = (Result<Output, Failure>) -> Void
public init(
_ attemptToFulfill: @escaping (@escaping Future<Output,
Failure>.Promise) -> Void
)
final public func receive<S>(subscriber: S) where Output == S.Input, Failure == S.Failure, S : Subscriber
}
The Future
class takes two generic types namely Output
and Failure
, it conforms to the Publisher
protocol and provides with an initializer which will encapsulate our implementation.
The initializer takes one argument that is an @escaping
function which itself takes a typealiased escaping function as an argument.
The Promise
typealias expects a Result
type — an enum with a success
and a failure
case — which will be used to model our response.
public enum Result<Success, Failure> where Failure : Error {
/// A success, storing a `Success` value.
case success(Success)
/// A failure, storing a `Failure` value.
case failure(Failure)
{…}
}
Implementing a HTTP request to create a future and return a promise — with a success of a failure — would result in the following code.
struct Post: Decodable {
let userId, id: Int
let title, body: String
}
enum Error: Swift.Error {
case invalidResponse(URLResponse?)
case emptyData
case invalidJSON(Swift.Error)
case network(Swift.Error)
}
let publisher = Future<[Post], Error> { promise in
let url = URL(
string: "https://jsonplaceholder.typicode.com/posts"
)!
let session = URLSession.shared
return session.dataTask(with: url) { (data, response, error)
in
if let error = error {
return promise(.failure(Error.network(error)))
}
guard
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
return promise(.failure(
Error.invalidResponse(response))
)
}
guard let data = data else {
return promise(.failure(Error.emptyData))
}
do {
let posts = try JSONDecoder().decode(
[Post].self, from: data
)
return promise(.success(posts))
} catch let error {
return promise(.failure(Error.invalidJSON(error)))
}
}.resume()
}
Futures may deliver elements to the underlying subscribers using the sink
or receive
methods.
publisher
.receive(on: RunLoop.main)
.sink(receiveCompletion: { _ in }) { (posts) in
print("There are \(posts.count) posts")
}
// prints There are 100 posts
— RxSwift
RxSwift introduced the concept of Traits
which includes a handful of Observable
variations such as Single
.
A Single
is a variation of Observable
that, instead of emitting a series of elements, is always guaranteed to emit either a single element or an error.
Single is Combine’s Future
equivalence.
func fetch() -> Single<[Post]> {
return Single<[Post]>.create { single in
let url = URL(
string: "https://jsonplaceholder.typicode.com/posts"
)!
let session = URLSession.shared
let dataTask = session.dataTask(with: url) {
(data, response, error) in
if let error = error {
single(.error(Error.network(error))
return
}
guard
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
single(.error(Error.invalidResponse(response)))
return
}
guard let data = data else {
single(.error(Error.emptyData))
return
}
do {
let posts = try JSONDecoder().decode(
[Post].self,
from: data
)
single(.success(posts))
} catch let error {
single(.error(Error.invalidJSON(error)))
}
}
dataTask.resume()
return Disposables.create {
dataTask.cancel()
}
}
}
The Single
will either emit a success or an error event before terminating.
The subscription provides a SingleEvent
enumeration which could be either .success
containing a element of the Single
’s type, or .error
.
No further events would be emitted beyond the first one.
fetch()
.subscribe(onSuccess: { posts in
print("There are \(posts.count) posts")
}, onError: { error in
print(error.localizedDescription)
})
.disposed(by: disposeBag)
// prints There are 100 posts
— Conclusion
Future
and Single
are recommended to retrieve a single value at a specific time (typically network requests) whereas Publisher
and Observable
are used to model an infinite stream of elements.