Typically, when we have multiple unrelated asynchronous tasks, we want to execute them concurrently and possibly combine the results when all tasks are completed. Occasionally, though, concurrent execution is not possible, and we need to execute each task only after the previous is completed. One example of this is calling an API that accepts only one connection (we ran into this when trying to upload multiple attachments via Zendesk SDK). The API that we want then is exactly the same as with concurrent execution:
func whenSerial<T>(resolved promises: [Promise<T>]) -> Guarantee<[Result<T>]>
or maybe even when
with an additional argument to tell whether the execution needs to be serial or concurrent. Unfortunately, this doesn't work because each promise starts executing as soon as it's created, so no matter what we would do in the body, the promises would still execute in parallel. So instead of an array of promises, we need to start with an array of values that we can transform into promises (for example, an array of images, where each image is transformed into an upload promise):
whenSerial<T, U>(values: [T], transform: @escaping (T) -> Promise<U>) -> Guarantee<[Result<U>]>
The implementation would iteratively transform each value into a promise, execute it, and append the result to the array of results. Since each task is asynchronous, we can't use a regular loop for this. One way around it is to use recursion to start the next task when the previous task completes. It would look very similar to tail recursion, except that the tail call is done when the promise completes. Here is the full implementation:
func whenSerial<T, U>(values: [T], transform: @escaping (T) -> Promise<U>) -> Guarantee<[Result<U>]> {
let (gr, seal) = Guarantee<[Result<U>]>.pending()
var res: [Result<U>] = []
func iter(index: Array<T>.Index) {
guard index != values.endIndex else {
seal(res)
return
}
transform(values[index]).pipe { result in
res.append(result)
iter(index: values.index(after: index))
}
}
iter(index: values.startIndex)
return gr
}