Search code examples
swiftescapingclosurestry-catchthrow

Throw Error in function taking escaping closure


I am trying to write a custom function that registers a new user in Firebase. I have imported Firebase in a class named DatabaseManager. There, I manage all database interaction. In this class I would like to have a custom function for adding users, that throws all Firebase errors. This is so i can use the function in a ViewController class where I can catch the errors and show alerts. However I cannot seem to get the function to work properly and I am not sure what I am doing wrong.

Here's my function:

enum createAccountError : Error{
        case emailInUse, weakPassword, networkError, unknownError
    }

    //Mark: create a user profile
    ///create account with email, password, username, phoneNumber, birthDate, name
    func createAccount(_ userModel: UserModel, _ password: String?, completion: @escaping (_ inner: ()throws -> Bool)->())  {
        Auth.auth().createUser(withEmail: userModel.email!, password: password!, completion: {(user, error) in
            if let error = error {
                if let errCode = AuthErrorCode(rawValue: error._code) {
                    switch errCode {
                    case .emailAlreadyInUse:
                        completion({throw createAccountError.emailInUse})
                    case .weakPassword:
                        completion({throw createAccountError.weakPassword})
                    case .networkError:
                        completion({throw createAccountError.networkError})
                    default:
                        completion({throw createAccountError.unknownError})
                    }
                }
                return
            } else {
                completion({return true})
            }
        })
    }

and here's how I have tried using it:

DatabaseManager.system.createAccount(user, password) { (( inner: ()throws -> Bool)->()) in
            do{
                let result = try inner()
            } catch .emailInUse{
                //show alert
            }
            }

Solution

  • I've created test function for demonstration and everything works properly

    // error type
    enum TestError: Error { case notFound, empty }
    // define typealias for eiser usage of complex function type
    typealias ThrowableCallback = () throws -> Bool
    
    func createAccount(_ shouldThrow: Bool, completion: @escaping (_ inner: ThrowableCallback) -> Void) {
      // call completion callback asynchronously
      DispatchQueue.main.async {
        if shouldThrow {
          // throw error
          completion({ throw TestError.notFound })
        }
        // return value
        completion({ return true })
      }
    }
    

    Usage:

    createAccount(true) { (inner: ThrowableCallback) -> Void in
            do {
              let success = try inner()
              print(success)
            } catch {
              print(error)
            }
          }
    

    UPD: I don't recommend using this technique for handling errors in asynchronous functions. Use separate callbacks for success and failure or Promises to gracefully handle asynchronous code (check this out for more info)

    UPD 2: Actual Solution

    typealias ThrowableCallback = () throws -> User 
    func createAccount(_ userModel: UserModel,
                       _ password: String,
                       completion: @escaping (_ inner: ThrowableCallback) -> Void) {
      Auth.auth().createUser(withEmail: userModel.email!, password: password!, completion: {(user, error) in 
        if let error = error { completion({ throw error }) } 
        else { completions({ return user! }) } 
      }) 
    }
    
    // usage
    createAccount(userModel, somePassword) { (inner: ThrowableCallback) -> Void in
            do {
              let createdUser = try inner()
            } catch {
              ler errCode = (error as NSError)._code
              switch errCode {
                 case .emailAlreadyInUse:
                   showAlert("Email is already in use")
                 case .weakPassword:
                   showAlert("please enter stronger password ")
                 case .networkError:
                   showAlert("it seams that there is no internet connection")
                 default:
                   showAlert("Error creating user. Please try again later")
                 }
            }
          }