Section 4/91 menit

4. Typed Throws dengan Generics

4. Typed Throws dengan Generics

Ini adalah killer feature dari typed throws — memungkinkan generic functions yang meneruskan error type secara transparan:

Pattern: Error Propagation di Generic Function

swift
// Sebelum Swift 6: harus throws(any Error), kehilangan tipe error
// extension Array {
//     func myMap<T>(_ transform: (Element) throws -> T) throws -> [T] { ... }
// }

// Swift 6: error type dari transform di-propagate secara tepat
extension Array {
    func myMap<T, E: Error>(_ transform: (Element) throws(E) -> T) throws(E) -> [T] {
        var result: [T] = []
        result.reserveCapacity(count)
        for element in self {
            result.append(try transform(element))
        }
        return result
    }
}

// Penggunaan 1: non-throwing transform → myMap tidak throws
let doubled = [1, 2, 3].myMap { $0 * 2 }  // tidak perlu try!
print(doubled)  // [2, 4, 6]

// Penggunaan 2: typed throwing transform → myMap propagates error type
enum ConversionError: Error { case overflow }
let parsed = try [" 1", "2", "3"].myMap { str throws(ConversionError) -> Int in
    guard let n = Int(str.trimmingCharacters(in: .whitespaces)) else {
        throw ConversionError.overflow
    }
    return n
}
// catch block tahu persis error-nya adalah ConversionError

Pattern: Result Type dengan Typed Error

swift
// Result<Value, E> sudah ada — sekarang bisa berpasangan dengan typed throws
func riskyOperation() throws(DomainError) -> Value {
    // ...
}

// Konversi mudah antara throws dan Result
let result: Result<Value, DomainError> = Result { try riskyOperation() }

// Dan sebaliknya
let value = try result.get()  // throws(DomainError)

Pattern: Async + Typed Throws

swift
actor NetworkClient {
    enum RequestError: Error {
        case noConnection
        case timeout
        case serverError(statusCode: Int)
        case invalidResponse
    }
    
    func fetch(url: URL) async throws(RequestError) -> Data {
        guard isConnected else { throw RequestError.noConnection }
        
        do {
            let (data, response) = try await URLSession.shared.data(from: url)
            
            guard let http = response as? HTTPURLResponse else {
                throw RequestError.invalidResponse
            }
            
            guard (200...299).contains(http.statusCode) else {
                throw RequestError.serverError(statusCode: http.statusCode)
            }
            
            return data
        } catch is URLError {
            throw RequestError.timeout
        } catch {
            throw RequestError.invalidResponse
        }
    }
    
    private var isConnected: Bool { true }
}

// Penggunaan:
let client = NetworkClient()
do {
    let data = try await client.fetch(url: someURL)
    process(data)
} catch {
    // error adalah NetworkClient.RequestError — tanpa cast
    switch error {
    case .noConnection: showOfflineUI()
    case .timeout: showRetryUI()
    case .serverError(let code): showServerError(code)
    case .invalidResponse: showParseError()
    }
}