简体   繁体   中英

how to properly throw and catch an error from swift async function

I have a function that executes an async task. Sometimes that task fails and throws an error. I'm having trouble catching that error from the calling function. The playground below captures the essence of the trouble I'm having.

import UIKit

Task {
    var newNum: Double = 99.9
    do {
        newNum = try await getMyNumber()
        print("newNum within do: \(newNum)")
    } catch MyErrors.BeingStupid { //never gets caught
        print("caught being stupid")
    } catch MyErrors.JustBecause { //does get caught if throw is uncommented
        print("caught just because")
    }
    print("newNum outside of do \(newNum)")
}
print("done with main")

func getMyNumber() async throws -> Double {
    let retNum:Double = 0
    
    Task{
        sleep(5)
        let myNum: Double = Double.random(in: (0...10))
        if myNum > 9 {
            print("greater than 9")
        } else {
            print("less than 9 -- should throw")
            throw MyErrors.BeingStupid // error doesn't get thrown? HOW DO I CATCH THIS?
        }
    }
//    throw MyErrors.JustBecause  // this *does* get caught if uncommented
    return retNum //function always returns
}

enum MyErrors: Error {
    case BeingStupid, JustBecause
}

How do I catch the error being thrown at the line commented "HOW DO I CATCH THIS" back in the calling function?

Task is for unstructured concurrency . If the intent is to simulate an asynchronous task, I would advise remaining within structured concurrency. So, use Task.sleep(nanoseconds:) instead of sleep() and eliminate the Task within getMyNumber :

func getMyNumber() async throws -> Double {
    try await Task.sleep(nanoseconds: 5 * NSEC_PER_SEC)   // better simulation of some asynchronous process

    let myNum = Double.random(in: 0...10)
    if myNum > 9 {
        print("greater than 9")
    } else {
        print("less than 9 -- should throw")
        throw MyErrors.beingStupid
    }

    return myNum
}

enum MyErrors: Error {
    case beingStupid, justBecause
}

If you stay within structured concurrency, errors that are thrown are easily caught.

For more information about the difference between structured and unstructured concurrency, see The Swift Programming Guide: Concurrency or WWDC 2021 video Explore structured concurrency in Swift


The above illustrates the standard structured concurrency pattern. If you really must use unstructured concurrency, you could try await the Task and, if it didn't throw an error, return its value , eg:

func getMyNumber() async throws -> Double {
    let task = Task.detached {
        sleep(5)                              // really bad idea ... never sleep ... especially never sleep on the main actor, which is why I used `Task.detached`

        let myNum = Double.random(in: 0...10)
        if myNum > 9 {
            print("greater than 9")
        } else {
            print("less than 9 -- should throw")
            throw MyErrors.beingStupid
        }

        return myNum
    }

    return try await task.value
}

Note, because we're running something slow and synchronous, we want it to run on a background thread, and therefore use Task.detached .

I only include this unstructured concurrency example for the sake of completeness. You most likely will want to remain within structured concurrency. That way, you enjoy not only a more concise implementation, but also automatic propagation of cancelation, etc.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM