Sampai titik ini kita memakai delay() tanpa membahas apa yang membuatnya istimewa. Jawabannya: delay adalah suspend function. Fungsi jenis ini adalah pilar coroutines — tanpa suspend, kode kita tidak bisa benar-benar ditangguhkan. Chapter ini membongkar apa itu suspend function, aturan di mana ia boleh dipanggil, dan bagaimana kompilator Kotlin mengubahnya menjadi bentuk Continuation Passing Style (CPS) di bytecode. Pemahaman ini menjadi fondasi untuk membaca signature API coroutine dan menulis fungsi asinkron yang idiomatik.
Deklarasi dan Aturan Pemanggilan #
Menandai sebuah fungsi dengan suspend membuatnya dapat memanggil fungsi suspend lain dan dapat ditangguhkan. Tanda suspend bukan sekadar anotasi — ia mengubah signature fungsi di bytecode. Berikut contoh nyata:
import kotlinx.coroutines.*
fun main() = runBlocking {
println("mulai")
val profil = ambilProfil(101)
println("profil: $profil")
println("selesai")
}
// suspend function yang memanggil suspend function lain
suspend fun ambilProfil(id: Int): String {
val nama = ambilNama(id)
val umur = ambilUmur(id)
return "Profil(nama=$nama, umur=$umur)"
}
suspend fun ambilNama(id: Int): String {
delay(50)
return "Budi-$id"
}
suspend fun ambilUmur(id: Int): Int {
delay(30)
return 25 + id % 10
}Output yang Dihasilkan:
mulai
profil: Profil(nama=Budi-101, umur=26)
selesaiMari kita telaah baris-baris kode di atas:
- Baris 4 (
runBlocking { ... }):runBlockingmenciptakan scope coroutine. Tanpa scope ini,main()reguler tidak bisa memanggilambilProfilkarenaambilProfiladalahsuspend function. - Baris 8 (
suspend fun ambilProfil(id: Int): String): modifiersuspendditempatkan sebelumfun. Fungsi ini boleh memanggilambilNamadanambilUmurkarena keduanya jugasuspend. - Baris 14 (
delay(50)): di dalamambilNama,delaymenangguhkan coroutine selama 50ms. Saat suspend, threadmaindapat melayani coroutine lain jika ada. - Baris 19 (
return 25 + id % 10): aritmetika reguler tetap bisa dipakai; tandasuspendtidak mengubah tipe kembalian. Hasilnya tetapIntbiasa, bukanFuture<Int>atauDeferred<Int>.
Aturan Pemanggilan #
Aturan dasar pemanggilan suspend function:
| Konteks pemanggilan | Boleh? | Catatan |
|---|---|---|
Di dalam suspend fun | Ya | Konteks default |
Di dalam coroutine builder (launch, async, runBlocking) | Ya | Builder menyediakan scope |
| Di dalam fungsi biasa non-suspend | Tidak | Kompilator menolak |
Di dalam ekspresi lambda yang menerima suspend lambda | Ya | Misalnya flow { emit(...) } |
fun proses(id: Int): String {
// error: AmbilProfil is suspend; no coroutine context
return ambilProfil(id)
}Untuk menjembatani dunia non-suspend ke dunia coroutine, kita memakai builder seperti runBlocking (untuk kode blocking entry-point) atau launch/async di dalam CoroutineScope yang sudah ada.
Continuation Passing Style di Bytecode #
Di level source, pemanggilan suspend fun terlihat seperti pemanggilan fungsi biasa. Namun di bytecode, kompilator Kotlin mengubah signature fungsi. Sebuah fungsi:
suspend fun ambilNama(id: Int): String { ... }diubah menjadi:
fun ambilNama(id: Int, continuation: Continuation<String>): String?Penambahan parameter Continuation memungkinkan runtime menyimpan dan memulihkan status lokal coroutine. Saat ambilNama menemui delay(50), ia menyimpan posisi saat ini ke continuation dan mengembalikan penanda COROUTINE_SUSPENDED. Setelah 50ms, runtime memanggil ambilNama kembali dengan continuation yang sama, dan fungsi melanjutkan dari titik penangguhan — bukan dari awal.
Setiap titik suspend dalam satu fungsi menjadi sebuah state dalam mesin status. Sebuah fungsi dengan tiga delay akan memiliki empat status (mulai, setelah delay 1, setelah delay 2, setelah delay 3). Status ditentukan oleh kompilator dan disimpan di continuation — bukan di stack thread.
Implikasi Praktis #
Transformasi CPS membawa konsekuensi yang harus dipahami penulis kode coroutine:
| Konsekuensi | Penjelasan |
|---|---|
| Variabel lokal bertahan | Variabel yang dideklarasikan sebelum titik suspend tetap bisa diakses setelah resume |
Tidak bisa memakai try/finally tradisional untuk resource | Pola try { } finally { close() } tetap bekerja, namun finally bisa jalan jauh setelah try masuk |
Pengecualian berpropagasi sebagai Throwable | Galat yang dilempar dari suspend fun diteruskan ke continuation sebagai exception |
| Heap allocation kecil per suspend | Setiap titik suspend umumnya menghasilkan satu objek continuation; pengoptimalan inline mengurangi ini |
Referensi Lanjutan #
- Composing suspending functions
— pola
suspendfunction,await, dan eksekusi berurutan/paralel - Coroutine basics
— dasar penulisan
suspend fun
Selanjutnya #
Dengan memahami suspend function, kita siap melihat koleksi builder coroutine yang lebih lengkap: runBlocking, launch, dan async. Tiga builder ini punya peran berbeda — memilih yang tepat menentukan apakah kode kita berjalan dengan benar. Detailnya pada chapter Coroutine Builders.