Future

Combine provides the Future publisher class to model a single asynchronous event. The Future acts like a promise. It can either fulfil its promise or fail to fulfil its promise. Those familiar with promises from frameworks like PromiseKit might liken Future to a promise.

The added benefit of using Future is that it is a publisher, so you can use them with operators and subscribers and easily integrate them into combine data streams.

A Future emits a single event

Apple’s documentation states that Future is:

A publisher that eventually produces a single value and then finishes or fails.

In a marble diagram, a single event followed by a completion or an error would look like this.

marble diagram complete and error

Create a Future

Let’s create a simple Future that waits 2 seconds and emits a random number.

Future is generic over an associated type of output and failure. In this example we will use <Int, Never> to emit an integer and never fail the stream.

The Future initialiser takes a closure that takes another closure, (Result<Int, Never>) -> Void, as its input. We will call this the promise. It is a promise to return a result that matches the generic types of the Future. We hold onto this promise in our Future closure and then call the promise with our result or error whenever we have finished our work.

Simple right?

wtf

It makes more sense in code. It looks like this.

Future<Int, Never> { promise in
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        let number = Int.random(in: 1...10)
        promise(.success(number))
    }
}

If we wanted to emit the failure event we simply call the promise like so

Future<Int, Never> { promise in
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        let number = Int.random(in: 1...10)
        promise(.failure(.someErrorCase))
    }
}

As you can see the Future initialiser provides you with a promise that you then call with a value or an error to emit something from the Future publisher.

When does Future process?

If we change the future publisher back to emitting a success and include some print statements we can look into how and when the future is processing its closures.

let futurePublisher = Future<Int, Never> { promise in
    print("๐Ÿ”ฎ Future began processing")
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        let number = Int.random(in: 1...10)
        print("๐Ÿ”ฎ Future emitted number: \(number)")
        promise(Result.success(number))
    }
}.print("๐Ÿ” Publisher event")

Compiling and running this code would create the following logs.

๐Ÿ”ฎ Future began processing
๐Ÿ”ฎ Future emitted number: 2

As you can see, when we create a Future it immediately starts processing.

There are no logs from .print("๐Ÿ” Publisher event"). No downstream subscribers would receive events. This is because there are no subscribers. Even though the Future is doing its work.

Next, we will add a simple subscriber using sink and print the received value.

let futurePublisher = Future<Int, Never> { promise in
    print("๐Ÿ”ฎ Future began processing")
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        let number = Int.random(in: 1...10)
        print("๐Ÿ”ฎ Future emitted number: \(number)")
        promise(Result.success(number))
    }
}.print("๐Ÿ” Publisher event")

futurePublisher
    .sink { print ("๐ŸŽง Future stream received: \($0)") }
    .store(in: &cancellables)

Now the logs look like this.

๐Ÿ”ฎ Future began processing
๐Ÿ” Publisher event: receive subscription: (Future)
๐Ÿ” Publisher event: request unlimited
๐Ÿ”ฎ Future emitted number: 3
๐Ÿ” Publisher event: receive value: (3)
๐ŸŽง Future stream received: 3
๐Ÿ” Publisher event: receive finished

The downstream subscriber clearly receives the emitted event.

API Call Example with Future

Because Future models a one-time asynchronous event that succeeds or fails it is often used to model an API call or network fetch. Here is how that might look.

func fetch(url: URL) -> AnyPublisher<[Post], Error> {
    Future { promise in
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                promise(.failure(error))
                return
            }
            
            do {
                let posts = try JSONDecoder().decode([Post].self, from: data!)
                promise(.success(posts))
            } catch {
                promise(.failure(error))
            }
        }.resume()
    }.eraseToAnyPublisher()
}

fetch(url: url)
    .sink(receiveCompletion: { completion in
        print(completion)
    }, receiveValue: { value in
        print(value)
    }).store(in: &cancellables)

This code leverages the traditional completion handler of a URLSessionDataTask to create a Future. The Future object is then type erased to an AnyPublisher for easy use with any downstream subscribers.

Remember that simply calling the fetch(url:) function will initiate an API call. Therefore, if you are using a function like fetch(url:) and not immediately subscribing to it, you may want to defer any work that the Future might do until something subscribes.

Deferred

The Deferred struct is a simple publisher that takes in a closure as a parameter. That closure must return a DeferredPublisher. It will call this closure only when a subscriber subscribes. In this way, it defers any publisher processing until a subscriber causes downstream demand for an event.

Deferred with one subscriber

When we looked at when a future processes we saw that the Future began its processing as soon as it was created. If we wrap the same code with Deferred we can see how this defers any processing until a subscription happens.

let deferredPublisher = Deferred {
    Deferred {
        Future<Int, Never> { promise in
            print("๐Ÿ”ฎ Future began processing")
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                let number = Int.random(in: 1...10)
                print("๐Ÿ”ฎ Future emitted number: \(number)")
                promise(Result.success(number))
            }
        }.print("๐Ÿ” Publisher event")
    }
}

This prints nothing to the console as nothing is subscribed. Deferred won’t even create the Future until there is a subscription.

defer making the future wait

Let’s add a subscriber.

let deferredPublisher = Deferred {
    Deferred {
        Future<Int, Never> { promise in
            print("๐Ÿ”ฎ Future began processing")
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                let number = Int.random(in: 1...10)
                print("๐Ÿ”ฎ Future emitted number: \(number)")
                promise(Result.success(number))
            }
        }.print("๐Ÿ” Publisher event")
    }
}

deferredPublisher
    .sink { print ("๐ŸŽง Future stream received: \($0)") }
    .store(in: &cancellables)

Now we get the same logs as when we created the Future without Deferred, but only because we have a definitive subscriber.

๐Ÿ”ฎ Future began processing
๐Ÿ” Publisher event: receive subscription: (Future)
๐Ÿ” Publisher event: request unlimited
๐Ÿ”ฎ Future emitted number: 7
๐Ÿ” Publisher event: receive value: (7)
๐ŸŽง Future stream received: 7
๐Ÿ” Publisher event: receive finished

Deferred with multiple subscribers

Let’s subscribe two subscribers to the same deferred publisher.

deferredPublisher
    .sink { print ("๐ŸŽง Future stream 1 received: \($0)") }
    .store(in: &cancellables)

deferredPublisher
    .sink { print ("๐ŸŽง Future stream 2 received: \($0)") }
    .store(in: &cancellables)

Here are the logs we get now.

๐Ÿ”ฎ Future began processing
๐Ÿ” Publisher event: receive subscription: (Future)
๐Ÿ” Publisher event: request unlimited
๐Ÿ”ฎ Future began processing
๐Ÿ” Publisher event: receive subscription: (Future)
๐Ÿ” Publisher event: request unlimited
๐Ÿ”ฎ Future emitted number: 9
๐Ÿ” Publisher event: receive value: (9)
๐ŸŽง Future stream 1 received: 9
๐Ÿ” Publisher event: receive finished
๐Ÿ”ฎ Future emitted number: 6
๐Ÿ” Publisher event: receive value: (6)
๐ŸŽง Future stream 2 received: 6
๐Ÿ” Publisher event: receive finished

Notice that the emitted events are different.

Each subscription makes the DeferredPublisher run its closure that creates a Future. Therefore each subscription gets a different Future and therefore a different emitted event.

Conclusion

Future acts like a promise to return some value in the future. It can also fail. It’s useful for creating publishers of one-time asynchronous events. We need to be conscious that Future does its processing as soon as it is created.

Deferred wraps the creation of any type of publisher and only creates the publisher when a subscriber is present. Used with Future deferred can defer the Future’s work until downstream subscribers are present.