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
- Gunakan async untuk I/O bound operations - HTTP requests, database, file I/O
- Jangan gunakan untuk CPU bound - Gunakan multiprocessing untuk kalkulasi berat
- Selalu await coroutine - Jangan lupa await, atau coroutine tidak akan berjalan
- Gunakan asyncio.gather() untuk paralel - Lebih efisien dari await berturut-turut
- 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:
- Banyak I/O operations (HTTP, database, files)
- Web servers (FastAPI, aiohttp)
- Web scraping banyak halaman
- Chat applications
- Real-time data processing
❌ Jangan gunakan async ketika:
- CPU intensive tasks (gunakan multiprocessing)
- Operasi sederhana tanpa I/O wait
- Script kecil yang tidak perlu concurrency