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.