How to get a Result from a task
How to get a Result from a task êŽë š
Updated for Xcode 15
If you want to read the return value from a Task
directly, you should read its value
using await
, or use try await
if it has a throwing operation. However, all tasks also have a result
property that returns an instance of Swiftâs Result
struct, generic over the type returned by the task as well as whether it might contain an error or not.
To demonstrate this, we could write some code that creates a task to fetch and decode a string from a URL. To start with weâre going to make this task throw errors if the download fails, or if the data canât be converted to a string.
Hereâs the code:
enum LoadError: Error {
case fetchFailed, decodeFailed
}
func fetchQuotes() async {
let downloadTask = Task { () -> String in
let url = URL(string: "https://hws.dev/quotes.txt")!
let data: Data
do {
(data, _) = try await URLSession.shared.data(from: url)
} catch {
throw LoadError.fetchFailed
}
if let string = String(data: data, encoding: .utf8) {
return string
} else {
throw LoadError.decodeFailed
}
}
let result = await downloadTask.result
do {
let string = try result.get()
print(string)
} catch LoadError.fetchFailed {
print("Unable to fetch the quotes.")
} catch LoadError.decodeFailed {
print("Unable to convert quotes to text.")
} catch {
print("Unknown error.")
}
}
await fetchQuotes()
Thereâs not a lot of code there, but there are a few things I want to point out as being important:
- Our task might return a string, but also might throw one of two errors. So, when we ask for its
result
property weâll be given aResult<String, Error>
. - Although we need to use
await
to get the result, we donât need to usetry
even though there could be errors there. This is because weâre just reading out the result, not trying to read the successful value. - We call
get()
on theResult
object to read the successful, but thatâs whentry
is needed because itâs when Swift checks whether an error occurred or not. - When it comes to catching errors, we need a âcatch everythingâ block at the end, even though we know weâll only throw
LoadError
.
That last point hits us because Swift isnât able to evaluate the task to see exactly what kinds of error are thrown inside, and thereâs no way of adding that annotation ourself because Swift doesnât support typed throws.
If you donât care what errors are thrown, or donât mind digging through Foundationâs various errors yourself, you can avoid handling errors in the task and just let them propagate up:
func fetchQuotes() async {
let downloadTask = Task { () -> String in
let url = URL(string: "https://hws.dev/quotes.txt")!
let (data, _) = try await URLSession.shared.data(from: url)
return String(decoding: data, as: UTF8.self)
}
let result = await downloadTask.result
do {
let string = try result.get()
print(string)
} catch {
print("Unknown error.")
}
}
await fetchQuotes()
The main take aways here are:
- All tasks can return a
Result
if you want. - For the error type, the
Result
will either containError
orNever
. - Although we need to use
await
to get the result, we donât need to usetry
until we try to get the success value inside.
Many places where Result
was useful are now better served through async/await, but Result
is still useful for storing in a single value the success or failure of some operation. Yes, in the code above we evaluate the result immediately for brevity, but the power of Result
is that itâs value you can pass around elsewhere in your code to deal with at a later time.