Akhir bulan, Siti harus memproses 500 laporan penjualan: setiap laporan dimuat, diringkas, lalu diarsipkan. Cara paling cepat di atas kertas adalah Promise.all atas 500 promise sekaligus — tetapi cara itu membuka 500 pekerjaan bersamaan: memori membengkak dan server tujuan menolak karena dibanjiri permintaan. Kecepatan maksimal bukan selalu keputusan terbaik; yang kita butuhkan adalah cara mengatur seberapa banyak pekerjaan berjalan sekaligus (concurrency).

Chapter ini membangun dua pola standar dari alat yang sudah kita kuasai: batch dengan Promise.all, dan antrean berurutan. Keduanya diuji nyata dengan keluaran yang deterministik.

Pola 1: Batch dengan Promise.all #

Idenya: pecah daftar tugas menjadi batch berukuran tetap — misalnya dua tugas per batch — proses satu batch penuh dengan Promise.all, tunggu selesai, baru buka batch berikutnya. Paralelisme maksimalnya selalu sama dengan ukuran batch.

Berkas contoh memakai top-level await (berkas .mjs, sama seperti contoh module di section Modules) agar alurnya lurus dari atas ke bawah:

// pool-batch.mjs
// Membatasi paralelisme: tugas diproses per batch dengan Promise.all.

function muatBerkas(nama, durasi) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(nama), durasi);
  });
}

// --- 1. membagi daftar tugas menjadi batch berukuran tetap ---
function bagiBatch(daftar, ukuran) {
  const hasil = [];
  for (let i = 0; i < daftar.length; i += ukuran) {
    hasil.push(daftar.slice(i, i + ukuran));
  }
  return hasil;
}

// --- 2. memproses batch demi batch ---
const daftarBerkas = [
  { nama: "laporan-q1.csv", durasi: 30 },
  { nama: "laporan-q2.csv", durasi: 20 },
  { nama: "laporan-q3.csv", durasi: 10 },
  { nama: "laporan-q4.csv", durasi: 20 },
  { nama: "ringkasan.csv", durasi: 40 },
];

let nomorBatch = 0;
for (const batch of bagiBatch(daftarBerkas, 2)) {
  nomorBatch += 1;
  console.log(`--- batch ${nomorBatch} (maksimal 2 berkas) ---`);

  const hasil = await Promise.all(
    batch.map(({ nama, durasi }) =>
      muatBerkas(nama, durasi).then((selesai) => {
        console.log("  berkas selesai dimuat:", selesai);
        return selesai;
      })
    )
  );

  console.log("  hasil batch (urutan input):", hasil.join(", "));
}

console.log(`semua ${daftarBerkas.length} berkas selesai diproses`);

Output yang Dihasilkan:

text
--- batch 1 (maksimal 2 berkas) ---
  berkas selesai dimuat: laporan-q2.csv
  berkas selesai dimuat: laporan-q1.csv
  hasil batch (urutan input): laporan-q1.csv, laporan-q2.csv
--- batch 2 (maksimal 2 berkas) ---
  berkas selesai dimuat: laporan-q3.csv
  berkas selesai dimuat: laporan-q4.csv
  hasil batch (urutan input): laporan-q3.csv, laporan-q4.csv
--- batch 3 (maksimal 2 berkas) ---
  berkas selesai dimuat: ringkasan.csv
  hasil batch (urutan input): ringkasan.csv
semua 5 berkas selesai diproses

Mari kita telaah baris-baris pentingnya:

  1. Baris 11–17 (bagiBatch): memotong array dengan slice sepanjang ukuran, menghasilkan array berisi array. Daftar lima berkas menjadi tiga batch.
  2. Baris 29–31 (loop batch): for-of menyusuri batch satu per satu; nomorBatch dihitung manual agar label keluaran pasti.
  3. Baris 33–40 (Promise.all per batch): maksimal dua muatan berjalan serentak. Destructuring parameter ({ nama, durasi }) langsung membongkar objek tugas.
  4. Baris 35–38 (then di dalam map): mencatat waktu selesai nyata. Pada batch 1, laporan-q2.csv (20 ms) tercatat sebelum laporan-q1.csv (30 ms) — urutan ini dipastikan oleh durasi yang kita tentukan sendiri, bukan perlombaan yang hasilnya bisa berubah-ubah.
  5. Baris 42 (hasil Promise.all): selalu mengikuti urutan input — laporan-q1.csv sebelum laporan-q2.csv — meski q2 selesai lebih dulu. Inilah yang membuat Promise.all aman untuk menyusun kembali hasil sesuai daftar awal.
  6. Baris 45 (log akhir): tercetak setelah loop selesai; await di dalam for-of menjamin batch berikutnya baru dibuka setelah batch sebelumnya tuntas.

Total waktu pola batch kira-kira menjumlahkan batch terlama dari tiap kelompok — pada contoh sekitar 30 + 20 + 40 milidetik. Angka pastinya bervariasi antar mesin, tetapi strukturnya pasti: maksimal dua muatan dalam satu waktu.

Pola 2: Antrean Berurutan #

Ketika tugas wajib satu demi satu — printer yang tidak boleh menerima dua pekerjaan bersamaan, atau API dengan kuota ketat — kita buang paralelisme sama sekali. Bentuknya antrean: array pekerjaan dikerjakan satu per satu lewat shift() sambil di-await:

js
function tunda(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

async function jalankanAntrean(daftarTugas) {
  const antrean = [...daftarTugas];
  while (antrean.length > 0) {
    const tugasKini = antrean.shift();
    const hasil = await tugasKini();
    console.log("selesai:", hasil);
  }
}

jalankanAntrean([
  () => tunda(20, "cetak struk #1"),
  () => tunda(10, "cetak struk #2"),
  () => tunda(30, "cetak struk #3"),
]).then(() => console.log("antrean kosong, printer beristirahat"));

Output yang Dihasilkan:

text
selesai: cetak struk #1
selesai: cetak struk #2
selesai: cetak struk #3
antrean kosong, printer beristirahat

while dengan shift() mengosongkan antrean dari depan, dan await menjamin tugas berikutnya tidak dimulai sebelum yang berjalan selesai. Total waktunya adalah jumlah seluruh durasi — 20 + 10 + 30 milidetik pada contoh.

Satu detail penting: antrean menampung function pembuat promise, bukan promise yang sudah jadi. Pekerjaan baru hanya dibuat tepat saat gilirannya tiba. Bila kita mendaftarkan 500 promise sekaligus lewat Promise.all, pekerjaannya justru dimulai serentak — justru itu yang ingin kita cegah.

Memilih Pola yang Tepat #

PolaParalelismeTotal waktu kira-kiraCocok ketika
Antrean berurutan1jumlah semua durasitugas wajib satu demi satu
Batch per chunkukuran batchjumlah batch terlama tiap kelompokbatasi beban server atau memori
Promise.all penuhseluruh tugasdurasi tugas terlamatugas sedikit dan memang mandiri

Section ini berangkat dari satu thread yang mudah terhalang, lalu berakhir dengan pola untuk mengatur banyak pekerjaan. Keterampilan yang sama langsung terpakai pada tahap berikutnya: mengambil data nyata dari server lewat fetch di section Network, dan merespons aksi pengguna lewat event DOM di section Browser Events — dua bentuk asinkron yang paling sering ditemui dalam pengembangan web.