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:

kotlin
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:

text
mulai
profil: Profil(nama=Budi-101, umur=26)
selesai

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (runBlocking { ... }): runBlocking menciptakan scope coroutine. Tanpa scope ini, main() reguler tidak bisa memanggil ambilProfil karena ambilProfil adalah suspend function.
  2. Baris 8 (suspend fun ambilProfil(id: Int): String): modifier suspend ditempatkan sebelum fun. Fungsi ini boleh memanggil ambilNama dan ambilUmur karena keduanya juga suspend.
  3. Baris 14 (delay(50)): di dalam ambilNama, delay menangguhkan coroutine selama 50ms. Saat suspend, thread main dapat melayani coroutine lain jika ada.
  4. Baris 19 (return 25 + id % 10): aritmetika reguler tetap bisa dipakai; tanda suspend tidak mengubah tipe kembalian. Hasilnya tetap Int biasa, bukan Future<Int> atau Deferred<Int>.

Aturan Pemanggilan #

Aturan dasar pemanggilan suspend function:

Konteks pemanggilanBoleh?Catatan
Di dalam suspend funYaKonteks default
Di dalam coroutine builder (launch, async, runBlocking)YaBuilder menyediakan scope
Di dalam fungsi biasa non-suspendTidakKompilator menolak
Di dalam ekspresi lambda yang menerima suspend lambdaYaMisalnya flow { emit(...) }
kotlin Contoh: kompilator menolak pemanggilan dari fungsi biasa
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:

kotlin Signature source
suspend fun ambilNama(id: Int): String { ... }

diubah menjadi:

kotlin Signature hasil transformasi CPS (ilustrasi JVM)
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:

KonsekuensiPenjelasan
Variabel lokal bertahanVariabel yang dideklarasikan sebelum titik suspend tetap bisa diakses setelah resume
Tidak bisa memakai try/finally tradisional untuk resourcePola try { } finally { close() } tetap bekerja, namun finally bisa jalan jauh setelah try masuk
Pengecualian berpropagasi sebagai ThrowableGalat yang dilempar dari suspend fun diteruskan ke continuation sebagai exception
Heap allocation kecil per suspendSetiap titik suspend umumnya menghasilkan satu objek continuation; pengoptimalan inline mengurangi ini

Referensi Lanjutan #

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.