Sampai di sini kita sudah memakai runBlocking, launch, dan async tanpa membahas perbedaannya secara formal. Ketiganya adalah coroutine builder — fungsi yang membuat coroutine baru dari sebuah blok suspend. Memilih builder yang salah dapat menyebabkan bug halus: hasil yang tidak ditunggu, thread yang terblokade, atau exception yang terlempar ke tempat tak terduga. Chapter ini membedah ketiga builder utama, kapan memakai masing-masing, dan jebakan umum yang sering dijumpai.
Tiga Builder Utama #
Kotlin coroutines menyediakan banyak builder, tetapi tiga yang paling sering dipakai:
| Builder | Mengembalikan | Memblokade thread? | Kapan dipakai |
|---|---|---|---|
runBlocking | Nilai blok T | Ya, sampai blok selesai | Entry-point JVM (main, test); menjembatani kode non-coroutine |
launch | Job | Tidak | Fire-and-forget; tugas tanpa nilai kembalian |
async | Deferred<T> | Tidak | Komputasi yang menghasilkan nilai; konkurensi paralel |
runBlocking adalah satu-satunya builder yang sengaja memblokade thread pemanggil. launch dan async segera kembali setelah menjadwalkan coroutine — pemanggil harus secara eksplisit menunggu jika perlu (lewat join() untuk launch, atau await() untuk async).
Contoh Bertiga dalam Satu Kode #
Kode berikut mendemonstrasikan ketiga builder dalam satu program:
import kotlinx.coroutines.*
fun main() = runBlocking {
// 1. runBlocking: memblokade thread sampai blok selesai
println("[runBlocking] mulai")
val total = runBlocking {
delay(50)
42
}
println("[runBlocking] hasil=$total")
// 2. launch: fire-and-forget, mengembalikan Job
println("[launch] mulai")
val job: Job = launch {
repeat(3) { i ->
println(" launch tugas-$i")
delay(30)
}
}
job.join()
println("[launch] selesai")
// 3. async: mengembalikan Deferred<T> yang bisa di-await
println("[async] mulai")
val deferredA: Deferred<String> = async {
delay(40)
"hasil-A"
}
val deferredB: Deferred<Int> = async {
delay(30)
7
}
val hasil = "${deferredA.await()} + ${deferredB.await()}"
println("[async] hasil=$hasil")
}Output yang Dihasilkan:
[runBlocking] mulai
[runBlocking] hasil=42
[launch] mulai
launch tugas-0
launch tugas-1
launch tugas-2
[launch] selesai
[async] mulai
[async] hasil=hasil-A + 7Mari kita telaah baris-baris kode di atas:
- Baris 4 (
fun main() = runBlocking { ... }):runBlockingdi sini membungkus seluruhmain()agar program tidak keluar sebelum semua coroutine anak selesai. TanparunBlockingterluar,main()dapat selesai dan JVM berhenti sebelumlaunchatauasyncselesai. - Baris 6-9 (
runBlocking { delay(50); 42 }):runBlockingbersarang mengembalikan nilai ekspresi terakhir (42). Pola ini jarang dipakai di produksi karena memblokade thread; biasanya dipakai di unit test untuk verifikasi. - Baris 13 (
launch { ... }): builderlaunchmenjalankan tiga iterasi dengandelay(30). Builder ini tidak mengembalikan nilai — yang dikembalikan adalahJobuntuk kontrol lifecycle. - Baris 19 (
job.join()): menunggulaunchselesai sebelum lanjut ke blokasync. Tanpajoin, urutan output bisa berbeda. - Baris 23-25 (
async { ... }):asyncsepertilaunchtetapi mengembalikanDeferred<T>. TipeTditentukan dari ekspresi terakhir blok —"hasil-A"jadiDeferred<String>,7jadiDeferred<Int>. - Baris 28 (
deferredA.await()):await()adalah suspend function yang menangguhkan sampai hasil tersedia. Karenaasyncsudah dimulai lebih awal,delay(40)di A dandelay(30)di B berjalan tumpang tindih — total waktu hanya ~40ms, bukan 70ms.
Kapan Memakai Apa #
Pilihan builder menentukan struktur kode. Berikut panduan praktis:
runBlocking: hanya untuk entry-point dan test. Hindari memakainya di dalam kode library karena akan memblokade thread pemanggil.launch: untuk side-effect — menulis ke log, memperbarui UI, menyimpan ke database. Pemanggil tidak peduli nilai kembalian.async: untuk komputasi yang menghasilkan nilai. Pemanggil perlu menunggu hasil lewatawait().
suspend fun ambilProfil(id: Int): Profil = coroutineScope {
val a = async { ambilPengguna(id) }
val b = async { ambilFoto(id) }
val c = async { ambilKota(id) }
Profil(a.await(), b.await(), c.await())
}Pola di atas adalah idiom standar: tiga async dimulai bersamaan, lalu ketiganya di-await secara berurutan. Total waktu mendekati maksimum dari tiga operasi, bukan jumlah ketiganya.
Jebakan: async tanpa await #
Salah satu kesalahan paling umum adalah membuat async dan langsung membuang referensinya:
suspend fun prosesSalah(id: Int) {
async { ambilData(id) } // hasil dibuang, exception terlempar ke scope
println("ini jalan dulu")
}Jika blok async melempar exception, exception tersebut akan terpropagasi ke scope induk (membatalkan coroutine saudara). Ini sering tidak diinginkan. Solusinya: selalu await() agar exception muncul di tempat yang diharapkan, atau ganti ke launch jika tidak butuh nilai.
Referensi Lanjutan #
- Coroutine basics
— panduan resmi builder
runBlocking,launch,async - Kotlin coroutines overview — tinjauan umum coroutine dan builder-nya
- kotlinx.coroutines core
— API reference library
kotlinx.coroutines
Selanjutnya #
Setelah mengenal builder, pertanyaan berikutnya adalah: bagaimana lifecycle coroutine dikelola agar tidak ada yang bocor? Jawabannya ada pada CoroutineScope — yang akan kita bahas pada chapter Coroutine Scope.