kodingup.camp

Beranda/JavaScript Mastery: Prototype, Class, Waktu, Error & Async

Dasar Alur Kerja Promise

Modul 12 dari 153 menit bacaPromise-Based Workflow Fundamentals

Isi modul

    Kenapa ini penting

    Banyak pekerjaan di aplikasi membutuhkan waktu yang tidak bisa dipastikan: mengambil data dari server, membaca file, menunggu respons pembayaran. Jika JavaScript menunggu dengan cara memblokir (diam sampai data datang), seluruh halaman akan membeku. Tombol tidak bisa diklik, animasi berhenti. Karena JavaScript hanya punya satu thread utama, ia harus asynchronous: memulai pekerjaan, lanjut mengerjakan hal lain, lalu diberi tahu saat pekerjaan selesai.

    Bayangkan kamu memesan makanan di food court. Kasir memberimu buzzer (alat getar). Kamu tidak berdiri menunggu di depan dapur, kamu duduk, mengobrol, bermain HP. Ketika makanan siap, buzzer bergetar. Jika ada masalah (bahan habis), buzzer juga memberi tahu. Promise adalah buzzer itu: object yang mewakili hasil yang akan datang, entah berhasil atau gagal.

    Promise adalah dasar dari fetch, async/await, dan hampir semua API modern. Menguasainya wajib sebelum kamu menyentuh framework apa pun.

    Konsep inti

    Dari callback ke callback hell

    Cara lama menangani asinkron adalah callback: function yang diberikan untuk dipanggil nanti. Masalah muncul saat langkah-langkah saling bergantung:

    login(user, (err, sesi) => {
      if (err) return tampilkanError(err);
      ambilProfil(sesi, (err, profil) => {
        if (err) return tampilkanError(err);
        ambilTransaksi(profil.id, (err, data) => {
          if (err) return tampilkanError(err);
          // ... makin ke kanan
        });
      });
    });
    

    Inilah callback hell atau "pyramid of doom": kode menjorok ke kanan, penanganan error diulang di setiap level, dan alurnya sulit dibaca.

    Tiga state Promise

    State Arti Berubah ke
    pending Masih menunggu fulfilled atau rejected
    fulfilled Berhasil, punya value Tidak berubah lagi
    rejected Gagal, punya reason (biasanya Error) Tidak berubah lagi

    Promise yang sudah fulfilled atau rejected disebut settled, dan statusnya tidak akan berubah lagi.

    then, catch, finally

    Yang membuat Promise hebat: setiap .then mengembalikan Promise baru. Apa pun yang kamu return di dalam .then menjadi value Promise berikutnya. Jika yang di-return adalah Promise, rantai akan menunggunya. Hasilnya, langkah berurutan bisa ditulis datar (chaining), dan satu .catch di akhir menangkap error dari langkah mana pun.

    Membuat Promise sendiri

    const p = new Promise((resolve, reject) => {
      // kerjakan sesuatu...
      if (berhasil) resolve(nilai);
      else reject(new Error("alasan"));
    });
    

    Function yang diberikan ke new Promise disebut executor dan dijalankan langsung (sinkron). Hanya panggilan resolve/reject pertama yang berlaku. Ada juga jalan pintas Promise.resolve(nilai) dan Promise.reject(err).

    Pola paling umum adalah promisify: membungkus API berbasis callback atau timer menjadi function yang mengembalikan Promise, misalnya tunggu(ms).

    Contoh kode

    // 1. Promise sendiri: tunggu beberapa ms
    function tunggu(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms));
    }
    
    // 2. Simulasi API yang bisa gagal
    const DB_USER = { sari: { id: 7, nama: "Sari Dewi" } };
    const DB_TRANSAKSI = { 7: [{ ket: "Gaji", jumlah: 8000000 }, { ket: "Kos", jumlah: -1500000 }] };
    
    function login(username) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          const user = DB_USER[username];
          if (user) resolve(user);
          else reject(new Error(`User "${username}" tidak terdaftar`));
        }, 100);
      });
    }
    function ambilTransaksi(userId) {
      return tunggu(100).then(() => DB_TRANSAKSI[userId] ?? []);
    }
    
    // 3. Chaining datar: tidak ada piramida
    console.log("Memulai...");
    login("sari")
      .then((user) => {
        console.log("Login sebagai", user.nama);
        return ambilTransaksi(user.id); // return Promise → rantai menunggu
      })
      .then((daftar) => {
        const saldo = daftar.reduce((s, t) => s + t.jumlah, 0);
        console.log(`Saldo: Rp${saldo.toLocaleString("id-ID")}`);
        return saldo; // return nilai biasa → jadi value then berikutnya
      })
      .then((saldo) => console.log(saldo > 5000000 ? "Kondisi sehat" : "Perlu berhemat"))
      .catch((err) => console.log("Gagal:", err.message)) // menangkap error dari langkah mana pun
      .finally(() => console.log("Proses login selesai"));
    console.log("Kode ini jalan duluan, tidak menunggu");
    // → Memulai...
    // → Kode ini jalan duluan, tidak menunggu
    // → Login sebagai Sari Dewi
    // → Saldo: Rp6.500.000
    // → Kondisi sehat
    // → Proses login selesai
    
    // 4. Jalur gagal
    tunggu(400).then(() =>
      login("budi")
        .then((user) => ambilTransaksi(user.id)) // dilewati karena login gagal
        .then(() => console.log("Tidak akan tampil"))
        .catch((err) => console.log("Gagal:", err.message))
    );
    // → Gagal: User "budi" tidak terdaftar
    
    // 5. Error yang dilempar di dalam then juga ditangkap catch
    tunggu(600)
      .then(() => {
        throw new Error("Kurs tidak valid");
      })
      .catch((err) => {
        console.log("Ditangkap:", err.message);
        return "pakai kurs cadangan"; // catch juga bisa memulihkan rantai
      })
      .then((pesan) => console.log("Lanjut dengan", pesan));
    // → Ditangkap: Kurs tidak valid
    // → Lanjut dengan pakai kurs cadangan
    
    // 6. Executor berjalan sinkron & hanya resolve pertama yang berlaku
    const p = new Promise((resolve, reject) => {
      console.log("Executor langsung jalan");
      resolve("pertama");
      resolve("kedua");      // diabaikan
      reject(new Error("x")); // diabaikan
    });
    tunggu(800).then(() => p.then((v) => console.log("Value:", v)));
    // → Executor langsung jalan   (muncul paling awal saat file dijalankan)
    // → Value: pertama
    
    // 7. Promisify fungsi bergaya callback (err, hasil)
    function bacaDataLama(id, callback) {
      setTimeout(() => (id > 0 ? callback(null, { id, isi: "data lama" }) : callback(new Error("id salah"))), 50);
    }
    function bacaData(id) {
      return new Promise((resolve, reject) => {
        bacaDataLama(id, (err, hasil) => (err ? reject(err) : resolve(hasil)));
      });
    }
    tunggu(1000).then(() => bacaData(5)).then((d) => console.log(d)); // → { id: 5, isi: 'data lama' }
    

    Perhatikan urutan output di contoh 3: "Kode ini jalan duluan" muncul sebelum hasil login. Promise tidak membuat kode menunggu; ia hanya menjadwalkan apa yang harus dikerjakan ketika hasilnya datang. Node.js menyediakan util.promisify untuk melakukan contoh 7 secara otomatis, dan banyak API Node modern sudah punya versi Promise (misalnya fs/promises).

    Kesalahan umum

    1. Lupa return di dalam .then. Tanpa return, then berikutnya menerima undefined dan tidak menunggu Promise di dalamnya. Ini bug Promise nomor satu.
    2. Menyarangkan then di dalam then (callback hell versi Promise). Kembalikan Promise dan lanjutkan rantainya secara datar.
    3. Tidak ada .catch. Penolakan yang tidak ditangani memunculkan Unhandled Promise Rejection; di Node.js modern ini menghentikan proses.
    4. Mengira kode setelah Promise menunggu hasilnya. let data; ambil().then(d => data = d); console.log(data) mencetak undefined. Pakai data di dalam then (atau await).
    5. Membungkus Promise dengan new Promise tanpa perlu (explicit construction anti-pattern). Jika function sudah mengembalikan Promise, langsung return saja.

    Latihan

    1. Mudah, Buat lemparKoin() yang mengembalikan Promise: setelah 500 ms, resolve "Angka" atau reject Error("Gambar") secara acak. Tangani keduanya.
    2. Sedang, Ubah alur callback hell login → ambilProfil → ambilTransaksi menjadi rantai Promise datar dengan satu .catch. Buat ketiga function sebagai Promise simulasi.
    3. Menantang, Tulis denganTimeout(promise, ms) yang reject dengan Error("Timeout") jika promise belum settled dalam ms milidetik. Gunakan new Promise dan setTimeout (tanpa Promise.race).
    Lihat solusi
    // 1
    const lemparKoin = () =>
      new Promise((resolve, reject) =>
        setTimeout(() => (Math.random() < 0.5 ? resolve("Angka") : reject(new Error("Gambar"))), 500));
    lemparKoin()
      .then((hasil) => console.log("Menang:", hasil))
      .catch((err) => console.log("Kalah:", err.message)); // → Menang: Angka  atau  Kalah: Gambar
    
    // 2
    const jeda = (ms, nilai) => new Promise((r) => setTimeout(() => r(nilai), ms));
    const masuk = (u) => (u ? jeda(50, { token: "t-1" }) : Promise.reject(new Error("Username kosong")));
    const ambilProfil = (sesi) => jeda(50, { id: 7, token: sesi.token });
    const ambilTrx = (profil) => jeda(50, [{ jumlah: 100000 }, { jumlah: -20000 }]);
    masuk("sari")
      .then(ambilProfil)
      .then(ambilTrx)
      .then((trx) => console.log("Jumlah transaksi:", trx.length)) // → Jumlah transaksi: 2
      .catch((e) => console.log("Error:", e.message));
    
    // 3
    function denganTimeout(promise, ms) {
      return new Promise((resolve, reject) => {
        const id = setTimeout(() => reject(new Error("Timeout")), ms);
        promise.then(
          (v) => { clearTimeout(id); resolve(v); },
          (e) => { clearTimeout(id); reject(e); },
        );
      });
    }
    denganTimeout(jeda(1000, "lambat"), 200).catch((e) => console.log(e.message)); // → Timeout
    denganTimeout(jeda(100, "cepat"), 200).then((v) => console.log(v));            // → cepat
    

    Ringkasan

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.