Lesson 86Error Handling
Result Type
What is Result?
The Result type is a generic enum that represents either success with a value or failure with an error. It's perfect for async operations and explicit error handling.
Result Structure
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
case success(Success)
case failure(Failure)
}
Key Methods
get()
Convert to throwing - throws on failure
map(_:)
Transform success value
flatMap(_:)
Chain Result-returning functions
mapError(_:)
Transform error type
Result vs throws
Use Result
- - Async completion handlers
- - Store errors for later
- - Multiple error paths
Use throws
- - Synchronous code
- - Simple propagation
- - Cleaner syntax
Converting Between
Result { try throwingFunc() }- throws to Resulttry result.get()- Result to throws
main.swift
// RESULT TYPE
// A type-safe way to handle success or failure
// RESULT DEFINITION
// enum Result<Success, Failure: Error> {
// case success(Success)
// case failure(Failure)
// }
// DEFINING ERRORS
enum NetworkError: Error {
case noInternet
case serverDown
case invalidURL
case timeout
}
// FUNCTION RETURNING RESULT
func fetchUser(id: Int) -> Result<String, NetworkError> {
if id < 0 {
return .failure(.invalidURL)
}
if id == 0 {
return .failure(.serverDown)
}
return .success("User \(id)")
}
// HANDLING RESULT WITH SWITCH
let result = fetchUser(id: 42)
switch result {
case .success(let user):
print("Got user: \(user)")
case .failure(let error):
print("Error: \(error)")
}
// USING GET() METHOD
// Converts Result to throwing expression
do {
let user = try result.get()
print("User: \(user)")
} catch {
print("Failed: \(error)")
}
// RESULT WITH MAP
let uppercased = result.map { $0.uppercased() }
// Result<String, NetworkError>
// RESULT WITH FLATMAP
func fetchPosts(for user: String) -> Result<[String], NetworkError> {
return .success(["Post 1", "Post 2"])
}
let posts = result.flatMap { user in
fetchPosts(for: user)
}
// RESULT WITH MAPERROR
enum AppError: Error {
case network(NetworkError)
case unknown
}
let appResult = result.mapError { networkError in
AppError.network(networkError)
}
// CREATING RESULT FROM THROWING
func riskyOperation() throws -> Int {
return 42
}
let fromThrowing = Result { try riskyOperation() }
// Result<Int, Error>
// RESULT IN COMPLETION HANDLERS
func fetchData(completion: @escaping (Result<Data, NetworkError>) -> Void) {
// Simulate async work
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
let success = true
if success {
completion(.success(Data()))
} else {
completion(.failure(.timeout))
}
}
}
// Calling
fetchData { result in
switch result {
case .success(let data):
print("Got \(data.count) bytes")
case .failure(let error):
print("Failed: \(error)")
}
}
// PRACTICAL EXAMPLE: API Client
struct User: Codable {
let id: Int
let name: String
}
enum APIError: Error {
case network(NetworkError)
case decoding(Error)
case invalidResponse
}
class APIClient {
func fetchUser(id: Int) -> Result<User, APIError> {
// Simulated
if id > 0 {
return .success(User(id: id, name: "John"))
} else {
return .failure(.invalidResponse)
}
}
}
// CHAINING RESULTS
let client = APIClient()
let userResult = client.fetchUser(id: 1)
.map { user in user.name }
.map { name in name.uppercased() }
// RESULT VS THROWS
// Use Result when:
// - Storing errors for later
// - Async completion handlers
// - Multiple error paths
// - Explicit error handling
// Use throws when:
// - Synchronous code
// - Simple error propagation
// - Cleaner syntax preferredTry It Yourself!
Create a downloadImage(url:) function that returns Result<Data, DownloadError> and use map to convert the data to a UIImage!