Be careful wrapping a throwing function in a Task


You’re more of a video kind of person? I’ve got you covered! Here’s a video with the same content than this article 🍿


Can you guess what’s wrong with this code?

We have an asynchronous throwing function and for the purpose of this example it will always throw an Error:

And then we have a synchronous throwing function that calls this asynchronous function through the use of a Task:

At first glance, this code looks perfectly fine.

However, it has one very tricky pitfall!

Let’s say that we call the function loginUser() inside a do block, in order to catch and handle the Error:

If we run this code, we’ll notice that, even though the Error is thrown, the code in the catch block never gets executed.

This might seem strange but this is actually a totally normal and intended behavior!

What’s happening is that the Error is indeed thrown, but it is caught by the Task.

And from that point, the Task has no way of re-throwing the Error to the function loginUser(), because the Task is running asynchronously and the function loginUser() has already finished executing!

And actually the reason why this confusion was made possible is because I’ve made a mistake when writing this code!

You see, I’ve declared the function loginUser() as throwing, but there’s actually no call to a throwing function inside its scope.

Using the keyword throws here is completely superfluous, and if I remove it we can see that the code still builds, but now we get a very clear warning that the catch block will never be executed:

I’ve myself seen this mistake find its way in a codebase, so I would definitely recommend that you check that you also haven’t made it yourself!

Here’s the code if you want to experiment with it:

import Foundation

enum AuthenticationError: Error {
    case invalidCredentials
}

func verifyUserCredentials() async throws {
    throw AuthenticationError.invalidCredentials
}

func loginUser() {
    Task {
        try await verifyUserCredentials()
    }
}

do {
    try loginUser()
} catch {
    print("Authentication failed: \(error)")
}
Previous
Previous

Using a Raw String to write JSON data

Next
Next

let is a lie (sometimes)