Bab 10: Promise & Async/Await
10.1 Pengantar: Callback
Kamu pesan makanan online. Kamu gak berdiri di depan restoran nunggu — kamu kasih nomor HP (callback), dan mereka telepon kamu kalau makanan sudah siap. Sementara itu, kamu bisa ngerjain hal lain.
Apa itu Asynchronous?
Di JavaScript, banyak operasi yang butuh waktu:
- Ambil data dari server
- Baca file
- Tunggu timer
JavaScript gak mau nunggu. Dia lanjut ke baris berikutnya. Ini namanya asynchronous.
console.log("1. Pesan makanan");
setTimeout(function() {
console.log("3. Makanan datang!"); // Ini jalan belakangan
}, 2000);
console.log("2. Sambil nonton TV"); // Ini jalan duluan!
// Output:
// 1. Pesan makanan
// 2. Sambil nonton TV
// 3. Makanan datang! (setelah 2 detik)Callback Pattern
Callback = fungsi yang dipanggil setelah operasi selesai.
function ambilData(url, callback) {
// Simulasi ambil data (butuh waktu)
setTimeout(function() {
let data = { nama: "Budi", umur: 25 };
callback(null, data); // null = gak ada error
}, 1000);
}
// Pakai
ambilData("/api/user", function(error, data) {
if (error) {
console.log("Gagal:", error);
} else {
console.log("Berhasil:", data.nama); // "Budi"
}
});Callback Hell (Piramida Doom)
Masalahnya: kalau ada banyak operasi berurutan, kodenya jadi bersarang dalam-dalam:
ambilUser(1, function(error, user) {
if (error) {
handleError(error);
} else {
ambilPesanan(user.id, function(error, pesanan) {
if (error) {
handleError(error);
} else {
ambilDetail(pesanan.id, function(error, detail) {
if (error) {
handleError(error);
} else {
// Akhirnya bisa proses...
console.log(detail);
}
});
}
});
}
});Ini susah dibaca, susah di-debug, dan susah di-maintain. Makanya diciptakan Promise.
10.2 Promise
Promise itu kayak struk antrian di restoran. Kamu dapat struk (promise), dan struk itu punya 3 kemungkinan:
- Pending — Masih diproses (nunggu)
- Fulfilled — Berhasil (makanan siap!)
- Rejected — Gagal (bahan habis, gak bisa masak)
Membuat Promise
let janji = new Promise(function(resolve, reject) {
// "Executor" — kode yang butuh waktu
setTimeout(function() {
// Berhasil? Panggil resolve
resolve("Data berhasil diambil!");
// Gagal? Panggil reject
// reject(new Error("Gagal ambil data"));
}, 1000);
});resolve(value)— Panggil ini kalau berhasilreject(error)— Panggil ini kalau gagal- Hanya satu yang bisa dipanggil (yang pertama menang)
Menggunakan Promise: .then, .catch, .finally
let janji = new Promise(function(resolve, reject) {
setTimeout(() => resolve("Halo!"), 1000);
});
// .then — kalau berhasil
janji.then(function(hasil) {
console.log(hasil); // "Halo!" (setelah 1 detik)
});
// .catch — kalau gagal
janji.catch(function(error) {
console.log(error.message);
});
// .finally — selalu jalan (mau berhasil atau gagal)
janji.finally(function() {
console.log("Selesai, mau berhasil atau gagal");
});Contoh Nyata
function ambilUser(id) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
if (id > 0) {
resolve({ id: id, nama: "Budi" });
} else {
reject(new Error("ID tidak valid"));
}
}, 1000);
});
}
// Pakai
ambilUser(1)
.then(user => console.log("User:", user.nama)) // "User: Budi"
.catch(err => console.log("Error:", err.message));
ambilUser(-1)
.then(user => console.log("User:", user.nama))
.catch(err => console.log("Error:", err.message)); // "Error: ID tidak valid"// resolve/reject hanya bisa dipanggil SEKALI
let janji = new Promise(function(resolve, reject) {
resolve("Pertama"); // ✅ Ini yang dianggap
resolve("Kedua"); // ❌ Diabaikan
reject("Error"); // ❌ Diabaikan
});
janji.then(console.log); // "Pertama"10.3 Promise Chaining
Bayangin assembly line di pabrik. Setiap stasiun ngerjain satu tugas, lalu hasilnya dikirim ke stasiun berikutnya. Itulah chaining — setiap .then menerima hasil dari .then sebelumnya.
Cara Kerja
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000);
})
.then(function(hasil) {
console.log(hasil); // 1
return hasil * 2; // Kirim ke .then berikutnya
})
.then(function(hasil) {
console.log(hasil); // 2
return hasil * 2;
})
.then(function(hasil) {
console.log(hasil); // 4
});Kunci: Setiap .then mengembalikan Promise baru. Nilai yang di-return jadi hasil Promise itu.
⚠️ Jebakan: Bukan Chaining
let janji = new Promise(resolve => resolve(1));
// INI BUKAN CHAINING — semua dapat nilai yang sama
janji.then(h => console.log(h)); // 1
janji.then(h => console.log(h)); // 1
janji.then(h => console.log(h)); // 1
// INI CHAINING — nilai mengalir
janji
.then(h => h * 2) // return 2
.then(h => h * 2) // return 4
.then(h => console.log(h)); // 4Return Promise di dalam .then
Kalau .then mengembalikan Promise, chain berikutnya nunggu sampai Promise itu selesai:
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(1000)
.then(() => {
console.log("1 detik");
return delay(1000); // Tunggu 1 detik lagi
})
.then(() => {
console.log("2 detik");
return delay(1000);
})
.then(() => {
console.log("3 detik");
});10.4 Error Handling di Promise
.catch Menangkap Semua Error di Atasnya
fetch("https://url-gak-ada.com")
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.log("Error:", err.message));
// Tangkap error dari fetch, .then pertama, ATAU .then keduaImplicit try...catch
Di dalam Promise, error otomatis jadi rejection:
// Dua kode ini SAMA
new Promise((resolve, reject) => {
throw new Error("Ups!");
}).catch(console.log); // Error: Ups!
new Promise((resolve, reject) => {
reject(new Error("Ups!"));
}).catch(console.log); // Error: Ups!Rethrowing di Promise
new Promise((resolve, reject) => {
throw new Error("Ups!");
})
.catch(err => {
if (err.message === "Ups!") {
console.log("Ditangani");
// Gak throw lagi = chain lanjut normal
} else {
throw err; // Lempar ke .catch berikutnya
}
})
.then(() => console.log("Lanjut normal"));⚠️ Jebakan: Error di setTimeout
// INI GAK KETANGKAP oleh .catch!
new Promise((resolve, reject) => {
setTimeout(() => {
throw new Error("Ups!"); // Error di luar "aliran" Promise
}, 1000);
}).catch(console.log); // Gak jalan!
// SOLUSI: Pakai reject
new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Ups!")); // ✅ Ini ketangkap
}, 1000);
}).catch(console.log); // Error: Ups!10.5 Promise API
Promise.all — Tunggu Semua Selesai
let janji1 = fetch("/api/user");
let janji2 = fetch("/api/pesanan");
let janji3 = fetch("/api/produk");
Promise.all([janji1, janji2, janji3])
.then(function(hasil) {
// hasil = array [response1, response2, response3]
console.log("Semua selesai!");
})
.catch(function(err) {
// Kalau SATU AJA gagal, langsung masuk sini
console.log("Ada yang gagal:", err.message);
});Contoh praktis:
let nama = ["budi", "ani", "citra"];
let requests = nama.map(n => fetch(`/api/user/${n}`));
Promise.all(requests)
.then(responses => {
console.log(`Semua ${responses.length} request berhasil`);
});Promise.allSettled — Tunggu Semua, Gak Peduli Gagal/Berhasil
Promise.allSettled([
fetch("/api/user"), // Berhasil
fetch("/url-gak-ada"), // Gagal
fetch("/api/produk") // Berhasil
]).then(results => {
results.forEach(result => {
if (result.status === "fulfilled") {
console.log("Berhasil:", result.value);
} else {
console.log("Gagal:", result.reason);
}
});
});Promise.race — Siapa Cepat Dia Dapat
Promise.race([
new Promise(resolve => setTimeout(() => resolve("Lambat"), 2000)),
new Promise(resolve => setTimeout(() => resolve("Cepat"), 1000)),
new Promise(resolve => setTimeout(() => resolve("Paling lambat"), 3000))
]).then(console.log); // "Cepat" (yang pertama selesai)Promise.any — Ambil yang Pertama BERHASIL
Promise.any([
new Promise((_, reject) => setTimeout(() => reject("Error 1"), 1000)),
new Promise(resolve => setTimeout(() => resolve("Berhasil!"), 2000)),
new Promise((_, reject) => setTimeout(() => reject("Error 2"), 3000))
]).then(console.log); // "Berhasil!" (yang pertama SUKSES)Bedanya dengan race: any skip yang gagal, race ambil yang pertama selesai (mau gagal atau berhasil).
10.6 Promisification
Konsep
Mengubah fungsi berbasis callback jadi fungsi berbasis Promise:
// Versi callback
function ambilData(url, callback) {
setTimeout(() => {
callback(null, { data: "halo" });
}, 1000);
}
// Versi Promise (promisified)
function ambilDataPromise(url) {
return new Promise((resolve, reject) => {
ambilData(url, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
// Sekarang bisa pakai .then
ambilDataPromise("/api")
.then(data => console.log(data));Helper Promisify
function promisify(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
}
// Pakai
let ambilDataP = promisify(ambilData);
ambilDataP("/api").then(console.log);10.7 Microtask
Konsep Singkat
Handler .then/.catch/.finally gak langsung jalan — mereka masuk microtask queue dan dieksekusi setelah kode saat ini selesai.
let janji = Promise.resolve("Selesai!");
janji.then(console.log); // (2) Jalan kedua
console.log("Kode biasa"); // (1) Jalan pertama
// Output:
// "Kode biasa"
// "Selesai!"Kenapa penting? Ini menjelaskan kenapa .then selalu jalan SETELAH kode sinkron selesai, bahkan kalau Promise-nya sudah resolved.
10.8 async/await
Kalau Promise itu kayak struk antrian, async/await itu kayak kamu bilang ke JavaScript: "Aku mau nunggu di sini sampai hasilnya datang, tapi tolong jangan freeze seluruh aplikasi ya."
async Function
Tambahkan async sebelum function → fungsi itu selalu return Promise:
async function sapa() {
return "Halo!";
}
// Sama dengan:
function sapa() {
return Promise.resolve("Halo!");
}
sapa().then(console.log); // "Halo!"await — Tunggu Promise Selesai
await hanya bisa dipakai di dalam async function:
async function ambilData() {
// await = "tunggu sampai selesai"
let response = await fetch("/api/user");
let data = await response.json();
console.log(data); // Data sudah siap!
return data;
}Tanpa async/await (pakai .then):
function ambilData() {
return fetch("/api/user")
.then(response => response.json())
.then(data => {
console.log(data);
return data;
});
}Lihat bedanya? async/await jauh lebih bersih dan mudah dibaca.
Error Handling dengan try...catch
async function ambilUser(id) {
try {
let response = await fetch(`/api/user/${id}`);
if (!response.ok) {
throw new Error(`HTTP Error: ${response.status}`);
}
let user = await response.json();
return user;
} catch (err) {
console.log("Gagal:", err.message);
return null;
}
}await + Promise.all — Parallel Requests
async function ambilSemua() {
// SALAH — satu per satu (lambat)
let user = await fetch("/api/user");
let pesanan = await fetch("/api/pesanan"); // Nunggu user selesai dulu
// BENAR — paralel (cepat)
let [user2, pesanan2] = await Promise.all([
fetch("/api/user"),
fetch("/api/pesanan")
]);
// Dua request jalan BERSAMAAN
}// await HANYA bisa di dalam async function
function biasa() {
let data = await fetch("/api"); // ❌ SyntaxError!
}
// SOLUSI 1: Bikin async
async function biasa() {
let data = await fetch("/api"); // ✅
}
// SOLUSI 2: Top-level await (di module)
// Di file .mjs atau <script type="module">
let data = await fetch("/api"); // ✅
// SOLUSI 3: IIFE
(async () => {
let data = await fetch("/api"); // ✅
})();Contoh Lengkap: Dari Callback → Promise → async/await
// 1. CALLBACK (lama, berantakan)
function getUser(id, callback) {
setTimeout(() => callback(null, { id, nama: "Budi" }), 1000);
}
getUser(1, (err, user) => {
console.log(user.nama);
});
// 2. PROMISE (lebih rapi)
function getUserPromise(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, nama: "Budi" }), 1000);
});
}
getUserPromise(1).then(user => console.log(user.nama));
// 3. ASYNC/AWAIT (paling bersih)
async function main() {
let user = await getUserPromise(1);
console.log(user.nama); // Kayak kode sinkron biasa!
}
main();Buat fungsi async yang:
- "Ambil" data user (simulasi dengan setTimeout 1 detik)
- "Ambil" pesanan user (simulasi dengan setTimeout 1 detik)
- Gabungkan hasilnya
- Gunakan
Promise.allsupaya paralel (total cuma 1 detik, bukan 2)
function simulasiAmbilUser(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, nama: "Budi" }), 1000);
});
}
function simulasiAmbilPesanan(userId) {
return new Promise(resolve => {
setTimeout(() => resolve([
{ item: "Laptop", harga: 15000000 },
{ item: "Mouse", harga: 200000 }
]), 1000);
});
}
async function ambilDataLengkap(userId) {
// Kode kamu di sini
// Gunakan Promise.all!
}
// Test:
ambilDataLengkap(1).then(hasil => {
console.log(hasil);
// { user: { id: 1, nama: "Budi" }, pesanan: [...] }
});Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.