Section 2/131 menit

2. async/await

2. async/await

Teori: Masalah yang Dipecahkan

Sebelum async/await, kode asinkron di Swift menggunakan completion handler:

swift
// SEBELUM — Completion handler: callback pyramid, error prone
func loadUserProfile(id: String,
                     completion: @escaping (Result<User, Error>) -> Void) {
    URLSession.shared.dataTask(with: makeRequest(id: id)) { data, response, error in
        if let error = error {
            completion(.failure(error))
            return
        }
        guard let data = data else {
            completion(.failure(AppError.noData))
            return
        }
        do {
            let user = try JSONDecoder().decode(User.self, from: data)
            completion(.success(user))
        } catch {
            completion(.failure(error))
        }
    }.resume()
}

// Pemanggil harus ingat memanggil completion di semua code path
loadUserProfile(id: "123") { result in
    switch result {
    case .success(let user):
        DispatchQueue.main.async { self.updateUI(user: user) }
    case .failure(let error):
        DispatchQueue.main.async { self.showError(error) }
    }
}

Masalah utama:

  • Mudah lupa memanggil completion → silent hang
  • Harus manual dispatch ke main thread untuk UI
  • Sulit dibaca karena alur tidak linear
  • Error handling terpencar

Sintaks async/await

swift
// SESUDAH — async/await: linear, mudah dibaca
func loadUserProfile(id: String) async throws -> User {
    let request = makeRequest(id: id)
    // await menangguhkan fungsi ini, thread tidak diblokir
    let (data, _) = try await URLSession.shared.data(for: request)
    return try JSONDecoder().decode(User.self, from: data)
}

// Cara memanggil dari konteks non-async (misal: UIKit action)
Task {
    do {
        let user = try await loadUserProfile(id: "123")
        await MainActor.run { self.updateUI(user: user) }
    } catch {
        await MainActor.run { self.showError(error) }
    }
}

Keyword penting:

  • async — tandai fungsi sebagai asinkron, bisa di-suspend
  • await — titik di mana fungsi boleh di-suspend, menunggu hasil
  • async throws — fungsi asinkron yang bisa gagal

Kapan Menggunakan async/await

Gunakan:

  • Operasi I/O: network request, baca/tulis file, database query
  • Fungsi yang memanggil fungsi async lain
  • Mengganti completion handler yang sudah ada

Jangan gunakan:

  • Komputasi CPU-intensive murni (gunakan Task.detached atau DispatchQueue global)
  • Kode yang harus backward-compatible sampai iOS 14 atau lebih lama

Real Use Case: NetworkService dengan URLSession

swift
// Models — Sendable agar aman dikirim lintas thread
struct User: Decodable, Sendable {
    let id: String
    let name: String
    let email: String
}

struct Post: Decodable, Sendable {
    let id: Int
    let title: String
    let body: String
}

// NetworkService — kumpulan pure async function
struct NetworkService {
    private let baseURL = URL(string: "https://jsonplaceholder.typicode.com")!
    private let decoder = JSONDecoder()

    func fetchUser(id: String) async throws -> User {
        let url = baseURL.appendingPathComponent("users/\(id)")
        let (data, response) = try await URLSession.shared.data(from: url)

        guard let http = response as? HTTPURLResponse,
              (200..<300).contains(http.statusCode) else {
            throw NetworkError.badResponse
        }

        return try decoder.decode(User.self, from: data)
    }

    func fetchPosts(userId: String) async throws -> [Post] {
        var components = URLComponents(url: baseURL.appendingPathComponent("posts"),
                                       resolvingAgainstBaseURL: false)!
        components.queryItems = [URLQueryItem(name: "userId", value: userId)]
        let (data, _) = try await URLSession.shared.data(from: components.url!)
        return try decoder.decode([Post].self, from: data)
    }
}

enum NetworkError: Error {
    case badResponse
}