mahiriteratorgeneratorMahir3 mnt baca

Iterator dan Generator: function* dan yield

Bagaimana cara membuat urutan angka 1 sampai 1 miliar tanpa menyimpan semuanya di memori? Atau daftar halaman API yang diambil satu per satu hanya saat dibutuhkan? Jawabannya: hasilkan nilai secara malas (lazy), satu...

Iterator dan Generator: function* dan yield

Bagaimana cara membuat urutan angka 1 sampai 1 miliar tanpa menyimpan semuanya di memori? Atau daftar halaman API yang diambil satu per satu hanya saat dibutuhkan? Jawabannya: hasilkan nilai secara malas (lazy), satu per satu, dengan generator.

Kenapa generator ada

Array menyimpan semua elemen sekaligus. Untuk data besar atau tak terbatas (angka tak berhingga, stream data sensor, paginasi API), itu mustahil atau boros. Generator adalah fungsi spesial yang bisa "jeda" dan "lanjut": ia menghasilkan satu nilai, berhenti, lalu melanjutkan dari titik yang sama saat diminta nilai berikutnya. Memori yang dipakai hanya untuk nilai saat ini, bukan seluruh urutan.

function*, yield, dan next()

Tandai fungsi dengan *, pakai yield untuk mengeluarkan nilai:

javascript
function* hitungMundur(n) {
  while (n > 0) {
    yield n; // jeda di sini, keluarkan n
    n--;
  }
}

const mundur = hitungMundur(3);
console.log(mundur.next()); // { value: 3, done: false }
console.log(mundur.next()); // { value: 2, done: false }
console.log(mundur.next()); // { value: 1, done: false }
console.log(mundur.next()); // { value: undefined, done: true }

Memanggil hitungMundur(3) tidak menjalankan apa pun, ia hanya membuat object generator. Setiap .next() menjalankan fungsi sampai yield berikutnya, lalu berhenti dan mengingat posisinya. done: true berarti urutan selesai.

Generator bisa dipakai langsung di for...of, yang otomatis memanggil .next() sampai selesai:

javascript
for (const angka of hitungMundur(3)) {
  console.log(angka); // 3, 2, 1
}

Contoh realistis: urutan tak terbatas dan paginasi malas

Nomor antrean warung tidak ada habisnya. Generator tak terbatas tidak masalah karena nilainya dibuat on-demand:

javascript
function* nomorAntrean() {
  let n = 1;
  while (true) { // tak terbatas, tapi aman karena lazy
    yield "A-" + String(n).padStart(3, "0");
    n++;
  }
}

const antrean = nomorAntrean();
console.log(antrean.next().value); // A-001
console.log(antrean.next().value); // A-002
console.log(antrean.next().value); // A-003
// tidak pernah kehabisan memori walau while(true)

Dan untuk paginasi API: ambil halaman berikutnya hanya saat halaman saat ini habis dibaca:

javascript
function* halamanProduk(semuaProduk, perHalaman) {
  for (let i = 0; i < semuaProduk.length; i += perHalaman) {
    yield semuaProduk.slice(i, i + perHalaman);
  }
}

const produk = ["kopi", "teh", "indomie", "roti", "susu", "es"];
const pager = halamanProduk(produk, 2);
console.log(pager.next().value); // ["kopi", "teh"]
console.log(pager.next().value); // ["indomie", "roti"]

Konsumen (misalnya tombol "Muat lagi") tinggal memanggil .next() saat user scroll ke bawah. Logika "halaman berapa sekarang" tersembunyi rapi di dalam generator.

Mengirim nilai masuk dengan next(nilai)

.next() bisa membawa nilai masuk yang diterima oleh yield sebagai expression. Ini fondasi cara kerja async/await di awal sejarah JavaScript:

javascript
function* dialog() {
  const nama = yield "Siapa namamu?";
  yield `Halo, ${nama}! Selamat datang.`;
}

const d = dialog();
console.log(d.next().value);        // "Siapa namamu?"
console.log(d.next("Ani").value);    // "Halo, Ani! Selamat datang."

Nilai "Ani" yang dikirim lewat .next("Ani") menjadi hasil dari expression yield "Siapa namamu?", lalu disimpan ke nama. Pola ini dipakai library seperti redux-saga untuk alur async yang kompleks.

Jebakan umum

  1. Generator tidak jalan sampai .next() pertama. Kode sebelum yield pertama tidak dieksekusi saat fungsi dipanggil. Bug umum: efek samping yang diharapkan langsung terjadi ternyata tertunda.
  2. Sekali habis, tidak bisa diulang. Generator yang sudah done: true tetap done selamanya. Buat generator baru dengan memanggil fungsi generatornya lagi.
  3. yield di arrow function tidak bisa. const f = *() => {} adalah syntax error. Generator harus function biasa (atau method class/shorthand object).
  4. Jangan bingung dengan async generator. async function* menghasilkan nilai promise secara lazy untuk stream async (misalnya baca file besar baris per baris). Pelajari setelah generator sinkron dikuasai.
Live Playground
export default function App(): JSX.Element {
  return <h1>Hello world</h1>
}

Edit kode di kiri, preview kanan ter-update otomatis.

Tantangan

Generator Bilangan Fibonacci

Di dunia nyata, deret tak terbatas muncul di banyak tempat: ID transaksi yang terus bertambah, data sensor yang mengalir, atau simulasi. Menyimpan semuanya di array akan menghabiskan memori. Tantangan ini melatihmu berpikir lazy. Buat generator function fibonacci() yang menghasilkan deret Fibonacci tanpa batas (0, 1, 1, 2, 3, 5, 8, ...), lalu tulis kode untuk mengambil 10 angka pertama memakai .next() dalam loop.

<!doctype html>
<html>
<head>
<meta charset="utf-8" />

</head>
<body>
<h1>Halo JS</h1>
<p>Buka console preview untuk melihat output.</p>
<script src="index.js"></script>
</body>
</html>