[Swift] Perform error handling with do-catch statement (Part 3)

This article is a continuation of [Swift] Do-catch statement to handle errors (Part 2).

In the previous article, I mentioned the try keyword. If you're not familiar with the try keyword, take a look. Also, if you don't understand do-catch, See [Swift] Perform error handling with do-catch statement (Part 1).

Let's continue.

When to use do-catch

Provide error details

In the do-catch statement, the catch clause receives the details of the error. ** Therefore, when an error occurs, it can be processed according to the details of the error. ** **

In that respect, it is similar to the Result \ <Success, Failure> type, but ** The Result \ <Success, Failure> type can only handle errors of the same type as the type argument Error. The do-catch statement can handle any error as long as it is a type that conforms to the Error protocol. ** **

Result \ <Success, Failure> type makes it easy to predict what kind of error there is, The do-catch statement has the advantage of being able to handle multiple types of error handling in one place.

Guarantee that it is either a success or a failure

Similar to Result \ <Success, Failure> type, error handling by do-catch statement is also available. The advantage is that the result of the process is ** narrowed down to either success or failure **.

The do clause does not continue to run despite the error, and It is impossible due to the language specification that the catch clause is executed even though no error has occurred.

In the following sample code No matter what value is passed as an argument, the return value will be either success or failure.


enum SampleError: Error {
    case someError
}

func someFunc(value: Int) -> String {
    do {
        guard value < 10 else {
            throw SampleError.someError
        }
        return "Success"
    } catch {
        return "Failure"
    }
}

print(someFunc(value: 1))
print(someFunc(value: 11))

Execution result
Success
Failure

Handles continuous processing errors

When performing continuous processing that may cause an error, The do-catch statement is easier to write than the Result \ <Success, Failure> type.

There are two processes that can cause an error this time. ** ① Search for a user by ID and process whether there is a corresponding user (2) Check if the format of the email address is correct, and if it is correct, get the value of the local port **

The local port is the testAddress part of [email protected].

First, write it with Result \ <Success, Failure> type.


import Foundation

enum DatabaseError: Error {
    case userNotFound
    case invalidEntry(reason:String)
}

struct User {
    var id: Int
    var name: String
    var email: String
}

//① Search for the user corresponding to the ID
func findById(byID id: Int) -> Result<User, DatabaseError> {
    for user in users {
        if user.id == id{
            return .success(user)
        }
    }
    return .failure(.userNotFound)
}

//(2) Check if the format of the email address is correct, and if it is correct, get the value of the local port.
func localPort(email: String) -> Result<String, DatabaseError> {
    //E-mail address "@Divide into two at the part
    let components = email.components(separatedBy: "@")
    guard components.count == 2 else {
        return .failure(.invalidEntry(reason: "The email address format is incorrect."))
    }
    return .success(components[0])
}

let user1 = User(id: 1, name: "Sato", email: "[email protected]")
let user2 = User(id: 2, name: "Tanaka", email: "tanaka.com")

let users = [user1, user2]

for user in users {
    switch findById(byID: user.id) {
    case .success(let user):
        switch localPort(email: user.email) {
        case .success(let localPort):
            print("\(user.name)The local port of your email address is\(localPort)is.")
        case .failure(let error):
            print("Error(\(user.name)Mr.): \(error)")
        }
    case .failure(let error):
        print("Error: \(error)")
    }
}

Execution result
The local port of Mr. Sato's email address is satou.
Error(Tanaka-san): invalidEntry(reason: "The email address format is incorrect.")

Next is the code when using do-catch.

In the case of the do-catch statement, the ** throws keyword ** is added to the function that can cause an error. And when I raise an error in a function, I use the ** throw keyword **.

In the do clause of the do-catch statement, the function is executed using the ** try keyword **. Mr. Tanaka moves to the catch clause because the format of the email address is incorrect.


import Foundation

enum DatabaseError: Error {
    case userNotFound
    case invalidEntry(reason:String)
}

struct User {
    var id: Int
    var name: String
    var email: String
}

func findById(byID id: Int) throws -> User {
    for user in users {
        if user.id == id{
            return user
        }
    }
    throw DatabaseError.userNotFound
}

func localPort(email: String) throws -> String {
    let components = email.components(separatedBy: "@")
    guard components.count == 2 else {
        throw DatabaseError.invalidEntry(reason: "The email address format is incorrect.")
    }
    return components[0]
}

let user1 = User(id: 1, name: "Sato", email: "[email protected]")
let user2 = User(id: 2, name: "Tanaka", email: "tanaka.com")

let users = [user1, user2]

for user in users {
    do {
        let user = try findById(byID: user.id)
        let local = try localPort(email: user.email)
        print("\(user.name)The local port of your email address is\(local)is.")
    } catch {
        print("Error(\(user.name)Mr.): \(error)")
    }
}

Execution result
The local port of Mr. Sato's email address is satou.
Error(Tanaka-san): invalidEntry(reason: "The email address format is incorrect.")

The number of lines of code is about the same, I think it's a more ** intuitive code **.

** However, please note that the do-catch statement cannot be used in asynchronous processing. ** **

Force error handling

The do-catch statement can force error handling. e? Isn't the Result \ <Success, Failure> type the same? You might have thought. In fact, I thought so at first.

** However, the Result \ <Success, Failure> type can actually ignore the error. ** **

For example, the sample code is as follows.

The checkAndAddUser () function gets a User type value as an argument. Compare the ID of the argument with the ID of the users array, If the ID is not covered, it is added to the array, and if the ID is covered, an error is generated.

The argument of this function is of type Result <Void, DatabaseError>. ** Since the value at the time of success is Void type, the value at the time of success is not accessed using the Switch statement. ** **

Not using a Switch statement also eliminates access to the failed value. I inadvertently forgot to write error handling! !! That is the possibility.


import Foundation

enum DatabaseError: Error {
    case userNotFound
    case duplicatedEntry
    case invalidEntry(reason:String)
}

struct User {
    var id: Int
    var name: String
}

var users = [
    User(id: 1, name: "Sato Tarou"),
    User(id: 2, name: "Tanaka Tarou")
]

func checkAndAddUser(argumentUser: User) -> Result<Void, DatabaseError> {
    for user in users {
        if user.id == argumentUser.id {
            return .failure(.duplicatedEntry)
        }
    }
    users.append(argumentUser)
    print("The following users have been added.\n User ID:\(argumentUser.id)\n Username:\(argumentUser.name)")
    return .success(())
}

let newUser1 = User(id: 3, name: "Kondou Tarou")
checkAndAddUser(argumentUser: newUser1)

let newUser2 = User(id: 2, name: "Hakase Tarou")
checkAndAddUser(argumentUser: newUser2)   //Processing failed but ignoring error

Execution result
The following users have been added.
User ID; 3
Username: Kondou Tarou

If you want to add error handling, you had to write something like this:


switch checkAndAddUser(argumentUser: newUser2) {
case .success():
    print("")
case .failure(let error):
    print(error)
}

On the other hand, the do-catch statement forces you to handle the error.

The do-catch statement does not matter what type the processing result is The compiler checks that the error is handled correctly.

** You must add the throws keyword when defining a function that is likely to cause an error, You must add the try keyword when defining a function that has the throws keyword. ** **

If the try keyword is attached, you will be aware of error handling. Error handling by the do-catch statement has a mechanism that makes it difficult to ignore the error.


import Foundation

enum DatabaseError: Error {
    case userNotFound
    case duplicatedEntry
    case invalidEntry(reason:String)
}

struct User {
    var id: Int
    var name: String
}

var users = [
    User(id: 1, name: "Sato Tarou"),
    User(id: 2, name: "Tanaka Tarou")
]

func checkAndAddUser(argumentUser: User) throws -> Void{
    for user in users {
        if user.id == argumentUser.id {
            throw DatabaseError.duplicatedEntry
        }
    }
    users.append(argumentUser)
    print("The following users have been added.\n User ID:\(argumentUser.id)\n Username:\(argumentUser.name)")
    return
}

let newUser1 = User(id: 3, name: "Kondou Tarou")
let newUser2 = User(id: 2, name: "Hakase Tarou")

do {
    try checkAndAddUser(argumentUser: newUser1)
    try checkAndAddUser(argumentUser: newUser2)
}

This concludes the explanation of the do-catch statement! I'm sorry that the explanation was as long as 3 articles.

I hope you've read this article and found a solution to your code.

Also, please have a look at the article about other error handling. -[Swift] Perform error handling with Optional \ type -[Swift] Perform error processing with Result \ <Success, Failure> type -[Swift] Termination of program by fatalError function[Swift] End of program by assertion

Thank you for watching until the end.

Recommended Posts

[Swift] Perform error handling with do-catch statement (Part 3)
[Swift] Perform error handling with do-catch statement (Part 2)
[Swift] Perform error handling with do-catch statement (Part 1)
[Swift] Perform error handling with Optional <Wrapped> type
[Swift] Error handling
[Swift] Perform error processing with Result <Success, Failure> type
error code 400 appears when deploying with release: perform
Customize REST API error response with Spring Boot (Part 2)
Customize REST API error response with Spring Boot (Part 1)