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.