dre_84w934
dre_84w934

Reputation: 738

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
            }
            }

Upvotes: 1

Views: 2899

Answers (2)

Andrew
Andrew

Reputation: 673

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")
             }
        }
      }

Upvotes: 1

Zach Fuller
Zach Fuller

Reputation: 1269

I would suggest refactoring your function as follows:

func createAccount(_ userModel: UserModel, _ password: String?, completion: @escaping(Error?) -> ())  {
    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(createAccountError.emailInUse)
                case .weakPassword:
                    completion(createAccountError.weakPassword)
                case .networkError:
                    completion(createAccountError.networkError)
                default:
                    completion(createAccountError.unknownError)
                }
            }

        } else {
            completion(nil)
        }
    })
}

Then when you call the function you can check if there was an error like this:

DatabaseManager.system.createAccount(user, password) { (error) in
   guard error == nil else {
      //Handle error
      return
   }

   //There was no error

Upvotes: 1

Related Questions