I use a token to complete a lot of tasks in my app. When my token expires, I want to send 1 token refresh network call to my backend even if multiple tasks fail and request the token at the same time. This was easy with completion handlers, as I could just send a network call on the first token refresh request and save subsequent token refresh request completion handlers in an array until the network call returned, then I'd call all the completion handlers with the new token. Is there a way to suspend multiple async/await function calls like that while I wait for the network call to complete? Is the answer to use continuations?
You should simply await
a saved Task
. E.g.:
actor NetworkManager {
var tokenTask: Task<String, Error>?
func token() async throws -> String {
if let tokenTask {
print("awaiting prior request")
return try await tokenTask.value
}
let task = Task {
print("starting request")
// perform network request here
}
tokenTask = task
return try await withTaskCancellationHandler {
try await task.value
} onCancel: {
task.cancel()
}
}
}
Note, because Task {…}
is unstructured concurrency, we want to wrap the awaiting of its value
in withTaskCancellationHandler
so that the caller has the option to cancel the task, should it ever need to do so. As a general rule, when using unstructured concurrency, we bear the burden of handling cancelation ourselves.
Apple introduced us to this pattern (of multiple await
on the same Task
) in the code accompanying WWDC 2021 video Protect mutable state with Swift actors.