Kita telah memakai async def, await, dan asyncio.run() sebagai satu paket. Sekarang saatnya membongkar isi paket itu: apa sebenarnya coroutine object, siapa yang menjalankannya, dan mengapa seluruh program async Python berporos pada satu penjadwal bernama event loop. Memahami tiga bagian ini membuat pesan error asyncio yang semula terasa misterius menjadi masuk akal.

Coroutine adalah Object yang Menunggu Digerakkan #

Coroutine bukan fungsi ajaib yang berjalan sendiri. Coroutine adalah object biasa yang menyimpan tiga hal: referensi ke kode yang akan dijalankan, status eksekusi, dan nilai yang sedang ditunggu. Kode berikut membuktikannya:

python
import asyncio

async def sapa():
    print("Halo dari dalam coroutine")
    return "selesai"

coro = sapa()
print("nama fungsi:", coro.cr_code.co_name)
hasil = asyncio.run(coro)
print("nilai kembali:", hasil)

Output yang Dihasilkan:

text
nama fungsi: sapa
Halo dari dalam coroutine
nilai kembali: selesai

Mari kita telaah apa yang terjadi pada baris-baris kode di atas:

  1. Baris 7 (coro = sapa()): interpreter membuat coroutine object tanpa menjalankan satu baris pun dari isi sapa.
  2. Baris 8 (print("nama fungsi:", coro.cr_code.co_name)): atribut cr_code masih menunjuk ke kode function sapa, sehingga tercetak sapa — coroutine menyimpan rencana eksekusinya, bukan hasilnya.
  3. Baris 9 (hasil = asyncio.run(coro)): asyncio.run() menggerakkan coroutine coro sampai selesai. Baru pada baris inilah "Halo dari dalam coroutine" dicetak.
  4. Baris 10 (print("nilai kembali:", hasil)): coroutine selesai dengan return "selesai", dan nilai tersebut menjadi hasil asyncio.run().

Yang menggerakkan coroutine di dalam kode async adalah await. Perhatikan urutan berikut:

python
import asyncio

async def lapor(nomor):
    print(f"laporan {nomor} dijalankan")

async def main():
    coro = lapor(1)
    print("coroutine dibuat, isi belum dijalankan")
    await coro
    print("coroutine selesai dijalankan")

asyncio.run(main())

Output yang Dihasilkan:

text
coroutine dibuat, isi belum dijalankan
laporan 1 dijalankan
coroutine selesai dijalankan

Baris coro = lapor(1) hanya membuat object. Isi lapor baru tereksekusi ketika await coro dijalankan. Dengan kata lain: await adalah penggerak coroutine — tanpa await (atau asyncio.run()), coroutine hanya barang jadi rencana yang tidak pernah bekerja.

Event Loop sebagai Penjadwal #

Siapa yang memutuskan coroutine mana yang maju? Jawabannya event loop: sebuah object penjadwal yang berjalan di satu thread dan terus melakukan siklus sederhana — pilih task yang siap, jalankan sampai task tersebut menyentuh await yang belum selesai, lalu pilih task berikutnya. Mirip pelayan restoran yang mengantar pesanan meja satu, lalu sambil menunggu masakan, mengantar pesanan meja lain. Batas analoginya: pelayan sejati bisa memutuskan pergi di tengah penyajian, sedangkan event loop tidak pernah menyela task — task-lah yang menyerahkan kontrol di await.

Karena hanya ada satu thread dan satu penjadwal, tidak ada dua baris coroutine yang berjalan pada saat yang sama, dan tidak ada task yang di-preempt di tengah baris. Seluruh pergantian terjadi di titik await. Konsekuensinya kita pelajari penuh pada Chapter Memahami async dan await.

asyncio.run: Titik Masuk yang Cukup #

Membuat dan mengelola event loop secara manual (asyncio.new_event_loop(), loop.run_forever()) adalah kode zaman lama. Sejak Python 3.7, satu function sudah mencukupi:

  • asyncio.run(main()) membuat event loop baru,
  • membungkus main() menjadi task dan menjalankannya sampai selesai,
  • lalu menutup event loop tersebut.

Pola baku program async adalah pola yang sudah kita pakai di seluruh chapter ini: definisikan async def main(), lalu panggil asyncio.run(main()) di baris terakhir skrip.

Dua Kesalahan Paling Umum #

Lupa await #

Coroutine yang dibuat tetapi tidak pernah di-await tidak menjalankan isinya, dan interpreter memberi tahu kita lewat RuntimeWarning ketika object tersebut dibersihkan:

python
async def sapa():
    print("Halo dari coroutine")

sapa()
print("Program selesai")

Output yang Dihasilkan (jalankan sebagai python3 lupa_await.py):

text
lupa_await.py:4: RuntimeWarning: coroutine 'sapa' was never awaited
  sapa()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Program selesai

Peringatan muncul sebelum baris print("Program selesai") karena object hasil sapa() langsung dibuang begitu statement selesai. Python menampilkan path lengkap berkas di tempat Anda menjalankan skrip; path di sini dipendekkan agar ringkas. Isi pesannya jelas: coroutine 'sapa' tidak pernah di-await.

await di luar function async #

Keyword await hanya sah di dalam async def. Menuliskannya di level modul ditolak interpreter bahkan sebelum program berjalan:

python
import asyncio

async def ambil():
    return 42

hasil = await ambil()

Output yang Dihasilkan (jalankan sebagai python3 await_modul.py):

text
  File "await_modul.py", line 6
    hasil = await ambil()
            ^^^^^^^^^^^^^
SyntaxError: 'await' outside function

Interpreter menolak seluruh berkas dengan SyntaxError — sama seperti error sintaks lainnya, tidak ada satu baris pun yang dieksekusi.

Dengan coroutine, await, dan event loop yang kini punya wajah jelas, kita beranjak ke kotak peralatannya: modul asyncio beserta sleep, gather, wait_for, to_thread, dan Queue pada Chapter asyncio.