Unable to throw errors within a closure and switch case

I am new to swift and looking for a way throw an error from my function below. The print statement works just as expected but I want to pass this error to the calling function (not included).

    func requestSignIn() -> Void {
        Auth0
            .webAuth()
            .scope("openid profile")
            .audience("<some url>")
            .audience("<some url>")
            .start { result in
                switch result {
                case .failure(let error):
                    print("authentication error \(error)")
                    **//throw error**
                case .success(let credentials):
                    self.accessToken = credentials.accessToken!
                    self.hasSession = true
                }
            }
    }```

When I uncomment the `throw error` line (in bold) I am getting this error and don't know how to resolve it.

`Invalid conversion from throwing function of type '(Result<Credentials>) throws -> Void' (aka '(Result<Credentials, Error>) throws -> ()') to non-throwing function type '(Result<Credentials>) -> Void' (aka '(Result<Credentials, Error>) -> ()')`

I have tried adding `throws` keyword in the function signature and a do-try-catch block in the calling function but that doesn't fix it.

Please let me know how to fix this since I am not sure whether this is occurring due to the closure or the switch case.

welcome.

it's the same way why you can't throw from within any escaping closure :

    DispatchQueue.main.async {
        throw NSError()
    }

you have to catch it right there:

    DispatchQueue.main.async {
        do {
            throw NSError()
        } catch {
            // ...
        }
    }

what people typically do is providing an escaping closure parameter like so:

func requestSignIn(execute: @escaping (Error?) -> Void) -> Void {
    Auth0
        .webAuth()
        .scope("openid profile")
        .audience("<some url>")
        .audience("<some url>")
        .start { result in
            switch result {
            case .failure(let error):
                print("authentication error \(error)")
                execute(error)
            case .success(let credentials):
                self.accessToken = credentials.accessToken!
                self.hasSession = true
                execute(nil)
            }
        }
}

or like so:

func requestSignIn(execute: @escaping (String?, Bool, Error?) -> Void) -> Void {
     ..............
        .start { result in
            switch result {
            case .failure(let error):
                print("authentication error \(error)")
                execute(nil, false, error)
            case .success(let credentials):
                execute(edentials.accessToken!, true, nil)
            }
        }

Thank you for the response! This clears up things a lot. However, I was wondering if there is a way to do this without the completion handler as you suggested. Combine framework maybe?

For my other URL tasks I am using the URLSession.shared.dataTaskPublisher and can throw errors within the tryMap() like so

    func accountInfoGet(accessToken: String) -> AnyPublisher<AccountInfoGetApiModel?, Error> {
        let relativePath = "accounts/me"
        let request = self.getUrlRequest(relativePath: relativePath, accessToken: accessToken)
        return URLSession.shared.dataTaskPublisher(for: request)
            .tryMap() {
                result -> Data in
                guard let httpResponse = result.response as? HTTPURLResponse,
                      httpResponse.statusCode == 200 else {
                    throw URLError(.badServerResponse)
                }
                return result.data
            }
            .decode(type: AccountInfoGetApiModel?.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }

To do the same for authentication will I need to create a custom publisher? Sorry if I am all over the place with this or if this needs to be it's own question.

yes, combine's tryMap swallows throws and convert them to errors to propagate further.

yes you may to follow the same pattern.

alternatively you may use "@Published" properties of your model class, like so:

class AccountInfoGetApiModel: ObservableObject {
    @Published var accessToken: String?
    @Published var hasSession: Bool = false
    @Published var error: Error?

    ....
    .start { result in
        switch result {
        case .failure(let error):
            print("authentication error \(error)")
            self.error = error
        case .success(let credentials):
            self.accessToken = credentials.accessToken!
            self.hasSession = true
        }
    }

published properties can be further used as publishers if needed:

accountInfoGetApiModel.$accessToken.sink { token in
    ...
}

or consumed by SwiftUI views as is.

a side on on self capturing styles

consider capturing 'self' explicitly to enable implicit 'self' in closures, vs referencing self explicitly "to make capture semantics explicit" (which IMHO doesn't make it quite "explicit"). i mean:

    .start { [self] result in // ******
        switch result {
        case .failure(let err):
            print("authentication error \(err)")
            error = err // ******
        case .success(let credentials):
            accessToken = credentials.accessToken! // ******
            hasSession = true // ******
        }
    }

people's mileage may vary on this one, some people prefer ".self" some "[self] in"

This is exactly what I was looking for. Thank you so much!

Terms of Service

Privacy Policy

Cookie Policy