A future is a tool for handling concurrency in a type-safe way. It's a wrapper around a primitive type like an Int
that allows the programmer to essentially say, "this will be an Int
, some time in the future". Often, the word "promise" is used interchangeably with "future" (as in "I promise this will be an Int
eventually"), although there are some semantic differences I won't discuss here.
In Swift, we can define a Future
like so:
let future = Future<Int, Never> { callback in
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
callback(.success(10))
}
}
Notice that the type signature here is Future<Int, Never>
. The Never
implies that this Future
will always succeed, but often this is used for error handling (e.g. Future<Response, Error>
).
We can get the value of this Future
by subscribing to it (this isn't necessarily the case in other frameworks, but Combine is built on the idea of publishers and subscribers. Publishers emit values, subscribers listen to those values.
There are a few different ways to create a subscriber, but the simplest is to call sink()
on the future, which returns an AnyCancellable
. More features are available by creating an AnySubscriber
instead.
let cancellable = future.sink { number in
print(number)
}
One caveat to Future
in Combine is that the code in block is executed eagerly, as soon as the future is defined, even if no object is subscribed. However, this behavior can be changed if we add the Deferred
wrapper around the definition.
let future = Deferred {
Future<Int, Never> { callback in
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
callback(.success(10))
}
}
}