简体   繁体   中英

Swift Async Error Handling for an API Call

I have code that calls an API to authenticate a user. The code is shown below:

        
        guard let url = URL(string: "https://somewebsiteapiurl/login") else {
            completion(.failure(NetworkError.badURL))
            return
        }
        
        let body = LoginRequestBody(username: username, password: password)
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try? JSONEncoder().encode(body)
        
        URLSession.shared.dataTask(with: request) { (data, response, error) in
            
            guard let data = data, error == nil else {
                completion(.failure(NetworkError.noData))
                return
            }
            
            guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data) else {
                completion(.failure(NetworkError.decodingError))
                return
            }
            
            guard let token = loginResponse.token else {
                completion(.failure(AuthenticationError.invalidCredentials))
                return
            }
            
            completion(.success(token))
            
        }.resume()
        
    }

Depending on the type of error I call the.failure with the respective Error case.

enum NetworkError: Error {
    case badURL
    case noData
    case decodingError
}



enum AuthenticationError: Error {
    case invalidCredentials
}

My first question is that is this a good way to do it. And also how would I handle these cases on the client side since sometimes the error comes from NetworkError enum and other times it is AuthenticationError.

This looks basically correct.

Personally, I would be inclined to dispatch completion handler back to the main queue, eg

func login(username: String, password: String, queue: DispatchQueue = .main, completion: @escaping (Result<String, Error>) -> Void) {
    guard let url = URL(string: "https://somewebsiteapiurl/login") else {
        queue.async { completion(.failure(NetworkError.badURL)) }
        return
    }

    let body = LoginRequestBody(username: username, password: password)

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.httpBody = try? JSONEncoder().encode(body)

    URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, error == nil else {
            queue.async { completion(.failure(error ?? NetworkError.noData)) }
            return
        }

        guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data) else {
            queue.async { completion(.failure(NetworkError.decodingError)) }
            return
        }

        guard let token = loginResponse.token else {
            queue.async { completion(.failure(AuthenticationError.invalidCredentials)) }
            return
        }

        queue.async { completion(.success(token)) }
    }.resume()
}

This way the caller does not have to dispatch stuff back to the main queue itself (and if the caller wants to use a queue other than the main queue, it can override that).

I would also pass back the Error that URLSession provides as shown above. That way the caller can differentiate between different types of network problems. Eg you might have a custom message for .notConnectedToInternet

For example:

login(username: username, password: password) { result in
    switch result {
    case .success(let token):
        // do something with `token`

    case .failure(AuthenticationError.invalidCredentials):
        // present invalid credentials UI

    case .failure(let error as URLError) where error.code == .notConnectedToInternet:
        // present some nice 'looks like you are offline' message

    case .failure(NetworkError.badURL), .failure(NetworkError.noData):
        // present some generic network error message
        // personally log these sorts of issues into Crashlytics, as this would suggest some programming bug, either in client app or in server, that DevOps or developers need to look into

    default:
        // present some generic network error message
    }
}

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