Async JavaScript Tuntas: Callback, Promise, sampai Async/Await
Evolusi cara JavaScript menangani operasi asinkron — dari callback hell sampai async/await yang bersih. Panduan komprehensif dengan gotcha yang perlu dihindari.
Kenapa JavaScript Perlu Mekanisme Async?
JavaScript adalah single-threaded — hanya ada satu call stack. Jika operasi yang lambat (network request, file I/O) berjalan synchronous, seluruh program akan freeze menunggu selesai. Mekanisme async memungkinkan JavaScript "melanjutkan hidup" sambil menunggu operasi lambat selesai.
Era 1: Callback
fs.readFile("data.json", "utf8", (err, data) => {
if (err) return handleError(err);
parseJSON(data, (err, parsed) => {
if (err) return handleError(err);
saveToDb(parsed, (err, result) => {
// Callback hell dimulai...
});
});
});
Masalah utama: callback hell (pyramid of doom), error handling yang repetitif, dan composability yang buruk.
Era 2: Promise
fs.promises.readFile("data.json", "utf8")
.then(data => parseJSON(data))
.then(parsed => saveToDb(parsed))
.then(result => console.log("Done:", result))
.catch(err => handleError(err)) // satu handler untuk semua error
.finally(() => cleanup());
Lebih bersih, tapi chaining panjang masih bisa membingungkan, dan error handling di dalam .then() tetap tricky.
Era 3: Async/Await
async function processData() {
try {
const raw = await fs.promises.readFile("data.json", "utf8");
const parsed = await parseJSON(raw);
const result = await saveToDb(parsed);
console.log("Done:", result);
} catch (err) {
handleError(err);
} finally {
cleanup();
}
}
Kode async yang dibaca seperti synchronous. Ini yang paling direkomendasikan untuk kode baru.
Gotcha yang Sering Bikin Bug
Await dalam Loop
// ❌ Sequential — lambat
for (const id of userIds) {
const user = await fetchUser(id); // tunggu satu per satu
}
// ✅ Parallel — jauh lebih cepat
const users = await Promise.all(userIds.map(id => fetchUser(id)));
Error dari Promise.all
// ❌ Satu gagal, semuanya gagal
const [a, b] = await Promise.all([fetchA(), fetchB()]);
// ✅ Handle kegagalan per-item
const results = await Promise.allSettled([fetchA(), fetchB()]);
results.forEach(r => {
if (r.status === "fulfilled") console.log(r.value);
else console.error(r.reason);
});
Async Tanpa Await
// ❌ Error tidak tertangkap — floating promise
someAsyncFunction(); // tidak ada await, error hilang
// ✅
await someAsyncFunction();
// atau jika sengaja fire-and-forget:
someAsyncFunction().catch(err => console.error(err));
Tip: AbortController untuk Cancelable Request
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000); // timeout 5 detik
try {
const res = await fetch("/api/data", { signal: controller.signal });
} catch (err) {
if (err.name === "AbortError") console.log("Request dibatalkan");
}