Updated for Xcode 14.2
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()
Download this as an Xcode project
There’s not a lot of code there, but there are a few things I want to point out as being important:
result
property we’ll be given a Result<String, Error>
.await
to get the result, we don’t need to use try
even though there could be errors there. This is because we’re just reading out the result, not trying to read the successful value.get()
on the Result
object to read the successful, but that’s when try
is needed because it’s when Swift checks whether an error occurred or not.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()
Download this as an Xcode project
The main take aways here are:
Result
if you want.Result
will either contain Error
or Never
.await
to get the result, we don’t need to use try
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.
SAVE 50% To celebrate WWDC23, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.