I am trying to create code that processes an array. For the purposes of this question we will assume it is an array of integers. The async function that processes them can either throw or do some work on them and return the values.
I have been having issues collecting the results after I create the task group as there really aren’t many methods that allow me to recover from throwing gracefully. .map() for example requires me to put try in front of the whole thing which means if any of the tasks throw I get out nothing.
In this case I expect on occasion a task will throw. If some task throws I really don’t care about it. I would just like to throw away the result and focus on the ones that don’t throw.
The only way I found to do this is to use nextResult()
which thankfully converts the thrown error into an enum that can be handled properly. But the resulting code from using it isn’t great. Is there a more eloquent way to write this code?
func asyncThrowOnOddNumbers(_ num: Int) async throws -> (Int, Int) {
if num % 2 == 1 {
throw NSError(domain: "OK", code: 1)
}
return (num, num * 10)
}
let numSet = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
let results = await withThrowingTaskGroup(of: (Int, Int).self) { group in
for num in numSet {
group.addTask {
return try await asyncThrowOnOddNumbers(num)
}
}
var successDir: [Int : Int] = [:]
while let workResult = await group.nextResult() {
switch workResult {
case .success(let value): successDir[value.0] = value.1
case .failure(_): continue
}
}
return successDir
}
// GOAL print "[2: 20, 6: 60, 10: 100, 8: 80, 4: 40]"
// Stretch goal get array with error or a number "[1: Error(), 2: 20, 3: Error(), 4: 40, 5: Error()..."
2
Answers
Perhaps you should not use a
ThrowingTaskGroup
, and use a non-throwing one instead.Make the child task’s type optional, to represent failed tasks as nil. Then you can use
try?
instead oftry
to get an optional.If you want the error as well, then your approach of using
nextResult
is probably the shortest way to do it.You said:
So, you want a dictionary keyed by an
Int
, whose value is either anInt
or anError
. The logical solution for the “value” would be aResult<Int, Error>
. Thus, the resulting dictionary would be a[Int: Result<Int, Error>]
:(As an aside, I don’t think it is the responsibility of the called function to create the tuple, but rather do that stuff within the
withTaskGroup
, so that’s the pattern I’ve employed above. But either way, hopefully you get the idea.)FWIW, if you just wanted a
[Int: Int]
that omits the thrown errors, you can do: