Materi Tutorial

Async Await Python

Asynchronous programming dengan async/await adalah paradigma pemrograman yang memungkinkan Anda menjalankan operasi secara non-blocking. Ini sangat berguna untuk aplikasi yang melakukan banyak operasi I/O seperti request HTTP, akses database, atau pembacaan file.

Mengapa Asynchronous?

Dalam pemrograman synchronous tradisional:

# Synchronous - menunggu satu per satu
hasil1 = ambil_data_dari_api()      # Tunggu 2 detik
hasil2 = ambil_data_dari_database() # Tunggu 2 detik
hasil3 = baca_file_besar()          # Tunggu 2 detik
# Total: 6 detik

Dengan asynchronous:

# Asynchronous - berjalan bersamaan
hasil1, hasil2, hasil3 = await asyncio.gather(
    ambil_data_dari_api(),
    ambil_data_dari_database(),
    baca_file_besar()
)
# Total: ~2 detik (paralel)

Konsep Dasar

Coroutine

Fungsi yang didefinisikan dengan async def disebut coroutine:

import asyncio

# Ini adalah coroutine
async def salam():
    print("Halo!")
    return "Selesai"

# Menjalankan coroutine
asyncio.run(salam())

await

await digunakan untuk menunggu hasil dari coroutine atau operasi async:

import asyncio

async def proses_lama():
    print("Mulai proses...")
    await asyncio.sleep(2)  # Simulasi operasi async
    print("Proses selesai!")
    return "Hasil"

async def main():
    hasil = await proses_lama()
    print(f"Mendapat: {hasil}")

asyncio.run(main())

Menjalankan Coroutine

Ada beberapa cara menjalankan coroutine:

import asyncio

async def hello():
    await asyncio.sleep(1)
    return "Hello!"

# Cara 1: asyncio.run() - untuk script standalone
if __name__ == "__main__":
    hasil = asyncio.run(hello())
    print(hasil)

# Cara 2: await - dari dalam coroutine lain
async def main():
    hasil = await hello()
    print(hasil)

Menjalankan Tasks Secara Bersamaan

asyncio.gather()

Menjalankan beberapa coroutine secara bersamaan:

import asyncio

async def download_file(nama: str, durasi: int) -> str:
    print(f"Mulai download {nama}...")
    await asyncio.sleep(durasi)
    print(f"Selesai download {nama}")
    return f"{nama} downloaded"

async def main():
    # Jalankan semua secara bersamaan
    hasil = await asyncio.gather(
        download_file("file1.txt", 2),
        download_file("file2.txt", 3),
        download_file("file3.txt", 1),
    )
    print(f"Semua hasil: {hasil}")

asyncio.run(main())
# Output:
# Mulai download file1.txt...
# Mulai download file2.txt...
# Mulai download file3.txt...
# Selesai download file3.txt (setelah 1 detik)
# Selesai download file1.txt (setelah 2 detik)
# Selesai download file2.txt (setelah 3 detik)
# Total waktu: ~3 detik (bukan 6 detik)

Perhatikan urutan outputnya! Meskipun kita memulai download file1 (2 detik) dan file2 (3 detik) terlebih dahulu, file3 (1 detik) selesai paling awal. Ini membuktikan bahwa ketiga tugas tersebut benar-benar berjalan secara bersamaan (tidak saling menunggu). Jika ini dijalankan secara synchronous, total waktu akan menjadi 2+3+1 = 6 detik.

asyncio.create_task()

Membuat task yang berjalan di background:

import asyncio

async def background_task():
    while True:
        print("Background task berjalan...")
        await asyncio.sleep(1)

async def main():
    # Buat task (tidak langsung dijalankan)
    task = asyncio.create_task(background_task())
    
    # Lakukan hal lain
    await asyncio.sleep(3)
    
    # Cancel task
    task.cancel()
    print("Task dibatalkan")

asyncio.run(main())

Dalam contoh di atas, fungsi background_task berjalan di latar belakang tanpa menghentikan eksekusi kode utama. Kita menggunakan create_task untuk menjadwalkannya, lalu sleep(3) di main memberikan waktu bagi task tersebut untuk berjalan beberapa kali sebelum akhirnya kita batalkan (cancel).

Async Context Manager

Untuk resource management dengan async:

import asyncio

class AsyncDatabaseConnection:
    async def __aenter__(self):
        print("Membuka koneksi database...")
        await asyncio.sleep(1)
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("Menutup koneksi database...")
        await asyncio.sleep(0.5)
    
    async def query(self, sql: str) -> list:
        await asyncio.sleep(0.5)
        return ["hasil1", "hasil2"]

async def main():
    async with AsyncDatabaseConnection() as db:
        hasil = await db.query("SELECT * FROM users")
        print(f"Query result: {hasil}")

asyncio.run(main())

Kode di atas mendemonstrasikan cara membuat objek yang bisa digunakan dengan async with. Metode __aenter__ dipanggil saat masuk blok, dan __aexit__ saat keluar. Ini sangat berguna untuk mengelola koneksi database atau sesi jaringan yang memerlukan proses setup dan teardown secara asynchronous.

Async Iterator

Untuk iterasi async:

import asyncio

class AsyncCounter:
    def __init__(self, max_count: int):
        self.max_count = max_count
        self.current = 0
    
    def __aiter__(self):
        return self
    
    async def __anext__(self):
        if self.current >= self.max_count:
            raise StopAsyncIteration
        await asyncio.sleep(0.5)
        self.current += 1
        return self.current

async def main():
    async for num in AsyncCounter(5):
        print(f"Count: {num}")

asyncio.run(main())

Async Generator

import asyncio

async def async_range(start: int, stop: int):
    for i in range(start, stop):
        await asyncio.sleep(0.5)
        yield i

async def main():
    async for num in async_range(1, 5):
        print(num)

asyncio.run(main())

Contoh Praktis: Async HTTP Requests

Menggunakan library aiohttp untuk HTTP requests async:

import asyncio
import aiohttp

async def fetch_url(session: aiohttp.ClientSession, url: str) -> dict:
    async with session.get(url) as response:
        return await response.json()

async def main():
    urls = [
        "https://api.github.com/users/python",
        "https://api.github.com/users/django",
        "https://api.github.com/users/fastapi",
    ]
    
    async with aiohttp.ClientSession() as session:
        # Fetch semua URL secara bersamaan
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        
        for result in results:
            print(f"User: {result.get('login')}")

# Install dulu: pip install aiohttp
asyncio.run(main())

Di sini kita menggunakan aiohttp (karena requests biasa tidak support async). Kita membuat satu ClientSession untuk efisiensi, lalu membuat daftar tugas (tasks) untuk setiap URL. asyncio.gather(*tasks) kemudian mengeksekusi semua request tersebut sekaligus. Bayangkan jika Anda harus mengambil data dari 100 URL; cara ini akan jauh lebih cepat daripada mengambilnya satu per satu.

Timeout dan Error Handling

import asyncio

async def operasi_lama():
    await asyncio.sleep(10)
    return "Selesai"

async def main():
    try:
        # Set timeout 2 detik
        hasil = await asyncio.wait_for(operasi_lama(), timeout=2.0)
        print(hasil)
    except asyncio.TimeoutError:
        print("Operasi timeout!")

asyncio.run(main())

Semaphore untuk Rate Limiting

Membatasi jumlah operasi bersamaan:

import asyncio

async def download(semaphore: asyncio.Semaphore, url: str):
    async with semaphore:  # Hanya N request bersamaan
        print(f"Downloading {url}...")
        await asyncio.sleep(2)
        print(f"Finished {url}")
        return url

async def main():
    # Maksimal 3 download bersamaan
    semaphore = asyncio.Semaphore(3)
    
    urls = [f"file_{i}.txt" for i in range(10)]
    tasks = [download(semaphore, url) for url in urls]
    
    await asyncio.gather(*tasks)

asyncio.run(main())

Semaphore bertindak seperti penjaga pintu. Di contoh ini, kita punya 10 file yang ingin didownload, tapi semaphore(3) hanya mengizinkan 3 download berjalan bersamaan. Segera setelah satu selesai, yang lain baru boleh masuk. Ini sangat penting agar server tujuan tidak memblokir IP kita karena terlalu banyak request dalam satu waktu.

Best Practices

  1. Gunakan async untuk I/O bound operations - HTTP requests, database, file I/O
  2. Jangan gunakan untuk CPU bound - Gunakan multiprocessing untuk kalkulasi berat
  3. Selalu await coroutine - Jangan lupa await, atau coroutine tidak akan berjalan
  4. Gunakan asyncio.gather() untuk paralel - Lebih efisien dari await berturut-turut
  5. Handle exceptions dengan baik - Gunakan try/except dalam coroutine
# ❌ Salah - Sequential
async def main():
    a = await task_a()  # Tunggu
    b = await task_b()  # Tunggu
    c = await task_c()  # Tunggu

# ✅ Benar - Parallel
async def main():
    a, b, c = await asyncio.gather(
        task_a(),
        task_b(),
        task_c()
    )

Kapan Menggunakan Async?

Gunakan async ketika:

Jangan gunakan async ketika:


Edit tutorial ini