Basic Tasks
Vending a Future Value
// Potentially long-running operation.
func performOperation() -> Deferred<Int> {
// 1. Create deferred.
let deferred = Deferred<Int>()
// 2. Kick off asynchronous code that will eventually…
let queue: DispatchQueue = /* … */
queue.async {
let result = computeResult()
// 3. … fill the deferred in with its value
deferred.fill(with: result)
}
// 4. Return the (currently still unfilled) deferred
return deferred
}
Taking Action when a Future Is Filled
You can use the upon(_:execute:)
method to run a function once the Deferred
has been filled. upon(_:execute:)
can be called multiple times, and the closures will be called in the order they were supplied to upon(_:execute:)
.
By default, upon(_:)
will run the closures on a background concurrent GCD queue. You can change this by passing a different queue when using the full upon(_:execute:)
method to specify a queue for the closure.
let deferredResult = performOperation()
deferredResult.upon { result in
print("got \(result)")
}
Peeking at the Current Value
Use the peek()
method to determine whether or not the Deferred
is currently
filled.
let deferredResult = performOperation()
if let result = deferredResult.peek() {
print("filled with \(result)")
} else {
print("currently unfilled")
}
Blocking on Fulfillment
Use the wait(until:)
method to wait for a Deferred
to be filled, and return the value.
// warning: Blocks the calling thread!
let result: Int = performOperation().wait(until: .distantFuture)!
Sequencing Deferreds
Monadic map(upon:transform:)
and andThen(upon:start:)
are available to chain Deferred
results. For example, suppose you have a method that asynchronously reads a string, and you want to call Int.init(_:)
on that string:
// Producer
func readString() -> Deferred<String> {
let deferredResult = Deferred<String>()
// call something async to fill deferredResult…
return deferredResult
}
// Consumer
let deferredInt: Future<Int?> = readString().map(upon: .any()) { Int($0) }
Combining Deferreds
There are three functions available for combining multiple Deferred
instances:
// MARK: and
// `and` creates a new future that is filled once both inputs are available:
let d1: Deferred<Int> = /* … */
let d2: Deferred<String> = /* … */
let dBoth: Future<(Int, String)> = d1.and(d2)
// MARK: allFilled
// `allFilled` creates a new future that is filled once all inputs are available.
// All of the input Deferreds must contain the same type.
var deferreds: [Deferred<Int>] = []
for i in 0 ..< 10 {
deferreds.append(/* … */)
}
// Once all 10 input deferreds are filled, the item at index `i` in the array passed to `upon` will contain the result of `deferreds[i]`.
let allDeferreds: Future<[Int]> = deferreds.allFilled
// MARK: firstFilled
// `firstFilled ` creates a new future that is filled once any one of its inputs is available.
// If multiple inputs become available simultaneously, no guarantee is made about which will be selected.
// Once any one of the 10 inputs is filled, `anyDeferred` will be filled with that value.
let anyDeferred: Future<Int> = deferreds. firstFilled()
Cancellation
Cancellation gets pretty ugly with callbacks.
You often have to fork a bunch of, Wait, has this been cancelled?
checks throughout your code.
With Deferred
, it’s nothing special:
You resolve the Deferred
to a default value,
and all the work waiting for your Deferred
to resolve
is unchanged. It’s still a Value
, whatever its provenance.
This is the power of regarding a Deferred<Value>
as just a Value
that
hasn’t quite been nailed down yet.
That solves cancellation for consumers of the value.
But generally you have some value-producer work you’d like to abort
on cancellation, like stopping a web request that’s in flight.
To do this, the producer adds an upon
closure to the Deferred<Value>
before vending it to your API consumer.
This closure is responsible for aborting the operation if needed.
Now, if someone defaults the Deferred<Value>
to some Value
,
the upon
closure will run and cancel the in-flight operation.
Let’s look at cancelling our fetchFriends(for:)
request:
// MARK: - Client
extension FriendsViewController {
private var friends: Deferred<Value>?
func refreshFriends() {
let friends = fetchFriends(for: jimbob)
friends.upon(.main) { friends in
let names = friends.map { $0.name }
dataSource.array = names
tableView.reloadData()
}
// Stash the `Deferred<Value>` for defaulting later.
self.friends = friends
}
func cancelFriends() {
friends?.fill(with: [])
}
}
// MARK: - Producer
func fetchFriends(for user: User) -> Deferred<[Friend]> {
let deferredFriends = Deferred<[Friend]>()
let session: NSURLSession = /* … */
let request: NSURLRequest = /* … */
let task = session.dataTaskWithRequest(request) { data, response, error in
let friends: [Friend] = parseFriends(data, response, error)
// fillIfUnfulfilled since we might be racing with another producer
// to fill this value
deferredFriends.fillIfUnfulfilled(friends)
}
// arrange to cancel on fill
deferredFriends.upon { [weak task] _ in
task?.cancel()
}
// start the operation that will eventually resolve the deferred value
task.resume()
// finally, pass the deferred value to the caller
return deferredFriends
}