Bab 10: Promise & Async/Await

4 menit baca

10.1 Pengantar: Callback

💡Analogi

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.

javascript
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.

javascript
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:

javascript
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

💡Analogi

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

javascript
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 berhasil
  • reject(error) — Panggil ini kalau gagal
  • Hanya satu yang bisa dipanggil (yang pertama menang)

Menggunakan Promise: .then, .catch, .finally

javascript
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

javascript
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"
⚠️Jebakan!
javascript
// 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

💡Analogi

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

javascript
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

javascript
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)); // 4

Return Promise di dalam .then

Kalau .then mengembalikan Promise, chain berikutnya nunggu sampai Promise itu selesai:

javascript
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

javascript
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 kedua

Implicit try...catch

Di dalam Promise, error otomatis jadi rejection:

javascript
// 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

javascript
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

javascript
// 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

javascript
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:

javascript
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

javascript
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

javascript
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

javascript
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:

javascript
// 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

javascript
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.

javascript
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

💡Analogi

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:

javascript
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:

javascript
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):

javascript
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

javascript
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

javascript
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
}
⚠️Jebakan!
javascript
// 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

javascript
// 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();
🎯Challenge

Buat fungsi async yang:

  1. "Ambil" data user (simulasi dengan setTimeout 1 detik)
  2. "Ambil" pesanan user (simulasi dengan setTimeout 1 detik)
  3. Gabungkan hasilnya
  4. Gunakan Promise.all supaya paralel (total cuma 1 detik, bukan 2)
javascript
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.