I have a function that executes an async task. Sometimes that task fails and throws an error. I'm having trouble catching that error from the calling function. The playground below captures the essence of the trouble I'm having.
import UIKit
Task {
var newNum: Double = 99.9
do {
newNum = try await getMyNumber()
print("newNum within do: \(newNum)")
} catch MyErrors.BeingStupid { //never gets caught
print("caught being stupid")
} catch MyErrors.JustBecause { //does get caught if throw is uncommented
print("caught just because")
}
print("newNum outside of do \(newNum)")
}
print("done with main")
func getMyNumber() async throws -> Double {
let retNum:Double = 0
Task{
sleep(5)
let myNum: Double = Double.random(in: (0...10))
if myNum > 9 {
print("greater than 9")
} else {
print("less than 9 -- should throw")
throw MyErrors.BeingStupid // error doesn't get thrown? HOW DO I CATCH THIS?
}
}
// throw MyErrors.JustBecause // this *does* get caught if uncommented
return retNum //function always returns
}
enum MyErrors: Error {
case BeingStupid, JustBecause
}
How do I catch the error being thrown at the line commented "HOW DO I CATCH THIS" back in the calling function?
CodePudding user response:
Task
is for unstructured concurrency. That means that you have to handle errors manually. But if your intent is to simulate an asynchronous task, then let us do that, but remain within structured concurrency. So, use Task.sleep(nanoseconds:)
instead of sleep()
and eliminate the Task
within getMyNumber
:
func getMyNumber() async throws -> Double {
try await Task.sleep(nanoseconds: 5 * NSEC_PER_SEC) // better simulation of some asynchronous process
let myNum = Double.random(in: 0...10)
if myNum > 9 {
print("greater than 9")
} else {
print("less than 9 -- should throw")
throw MyErrors.beingStupid
}
return myNum
}
enum MyErrors: Error {
case beingStupid, justBecause
}
If you stay within structured concurrency, errors that are thrown are easily caught.
For more information about the difference between structured and unstructured concurrency, see The Swift Programming Guide: Concurrency or WWDC 2021 video Explore structured concurrency in Swift
The above illustrates the standard structured concurrency pattern. If you really must use unstructured concurrency, you could try await
the Task
and, if it didn't throw an error, return its value
, e.g.:
func getMyNumber() async throws -> Double {
let task = Task.detached {
sleep(5) // really bad idea ... never sleep ... especially never sleep on the main actor, which is why I used `Task.detached`
let myNum = Double.random(in: 0...10)
if myNum > 9 {
print("greater than 9")
} else {
print("less than 9 -- should throw")
throw MyErrors.beingStupid
}
return myNum
}
return try await task.value
}
Note, because we’re running something slow and synchronous, we want it to run on a background thread, and therefore use Task.detached
.
I only include this unstructured concurrency example for the sake of completeness. You most likely will want to remain within structured concurrency. That way, you enjoy not only a more concise implementation, but also automatic propagation of cancelation, etc.