So far, we’ve seen a few code examples that contained tasks that could throw an error. We’ve also seen a cancellation check that throws a CancellationError and stops executing. Those are the first learnings that prepared you for this dedicated lesson on error handling with concurrency tasks.
Understanding Task failure
Tasks are pretty smart and know based on their body definition whether it’s a task that can fail or not. The following two task examples demonstrate this matter:
let throwingTask: Task<String, Error> = Task { () -> String in
throw URLError(.badURL)
}
let nonthrowingTask: Task<String, Never> = Task { () -> String in
"Hello, World!"
}
Both tasks have a Success type of String, but only the first task expects an error. The nonthrowing task has an error type of Never, which is the equivalent of a task that won’t throw errors.
Once an error is thrown inside the body of a task, further execution will be prevented.
Handling failures inside a Task
You can propagate errors up the chain or handle them inside the body of your asynchronous method:
let handlingErrorsInBody: Task<String, Never> = Task {
do {
guard !inputURL.isFileURL else {
throw URLError(.badURL)
}
/// .. Simulate network request
return "Fetched data from \(inputURL)"
} catch {
return "Fetching data failed for \(inputURL) with error \(error)"
}
}
This is similar to deciding whether to push an error up the chain from inside a regular method or to use a try-catch inline. Note that we’ve now created a Task with a failure error type of Never. In other words, it’s no longer a task that will throw an error up the stream.
Awaiting the result for a task and catching potential errors
If at any case you want to await a value and catch an error for a specific task reference, you can use the value property as follows:
let throwingTask: Task<String, Error> = Task { () -> String in
throw URLError(.badURL)
}
do {
let outputValue = try await throwingTask.value
} catch {
print("The task failed with error \(error)")
}
The output value will be awaited, and any thrown errors will be propagated into the catch closure.
Typed throws and Tasks
Unfortunately, at the time I’m writing this lesson, tasks in Swift don’t support typed throws. You would expect the above example to result in something like:
let throwingTask: Task<String, URLError> = Task { () -> String in
throw URLError(.badURL)
}
But you’ll run into the following compiler error:
This is caused by the method definition of the Task initializers. For example:
public init(priority: TaskPriority? = nil, operation: sending @escaping @isolated(any) () async throws -> Success)
Should have been defined as:
public init(priority: TaskPriority? = nil, operation: sending @escaping @isolated(any) () async throws(Failure) -> Success)
Note the throws(Failure) that I’ve added. I would not be surprised if this is being implemented in a future Swift 6.x.x release.
Summary
Error handling in tasks is pretty straightforward and works similarly to regular methods that can throw. Typed throws aren’t available yet, but they are expected in a future Swift release.
In the next lesson, we will look into detached tasks and how they differ from the regular tasks we’ve been using so far.