Section 9/171 menit

9. Best Practice: Error Handling & Cancellation

9. Best Practice: Error Handling & Cancellation

BP 9.1: Bedakan CancellationError dengan Error Lain

Cancellation bukan error production. Jangan log atau alert.

swift
// ✓ Pisahkan handling
do {
    let data = try await fetchData()
    self.data = data
} catch is CancellationError {
    // diam — ini bagian normal lifecycle
} catch let error as NetworkError {
    self.errorAlert = error.userFacingMessage
} catch {
    self.errorAlert = "Terjadi kesalahan tak terduga"
    logger.error("Unexpected: \(error)")
}

BP 9.2: Convert Completion Handler dengan withCheckedContinuation

Untuk bridging dengan API callback lama, gunakan withCheckedContinuation (atau withCheckedThrowingContinuation). Selalu pastikan continuation di-resume persis sekali.

swift
// ✓ Bridging callback API ke async
func loadLegacy() async throws -> Data {
    try await withCheckedThrowingContinuation { cont in
        legacyAPI.load { result in
            switch result {
            case .success(let data): cont.resume(returning: data)
            case .failure(let err): cont.resume(throwing: err)
            }
        }
    }
}

Hati-hati: API legacy yang tidak guaranteed memanggil callback (atau memanggil dua kali) akan menyebabkan crash. Pakai withCheckedContinuation (bukan withUnsafeContinuation) untuk diagnostic runtime.

BP 9.3: Typed Throws untuk API yang Stabil

Untuk function dengan error space yang kecil dan stable, pakai typed throws (Swift 6).

swift
enum ParseError: Error {
    case malformed
    case unsupportedVersion
}

func parse(_ data: Data) throws(ParseError) -> Config {
    // ...
}

Caller jadi tahu persis error apa yang mungkin, tanpa harus catch let error as ParseError.

BP 9.4: Cancellation Propagate di Custom AsyncSequence

Saat membuat AsyncSequence kustom, pastikan iterator respect cancellation.

swift
struct PolledSequence: AsyncSequence, Sendable {
    typealias Element = Int
    let interval: Duration
    let max: Int

    func makeAsyncIterator() -> Iterator {
        Iterator(interval: interval, max: max, count: 0)
    }

    struct Iterator: AsyncIteratorProtocol {
        let interval: Duration
        let max: Int
        var count: Int

        mutating func next() async throws -> Int? {
            if count >= max { return nil }
            try await Task.sleep(for: interval)  // sleep respect cancellation
            count += 1
            return count
        }
    }
}