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:
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:
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:
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:
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:
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
- Generator tidak jalan sampai
.next()pertama. Kode sebelumyieldpertama tidak dieksekusi saat fungsi dipanggil. Bug umum: efek samping yang diharapkan langsung terjadi ternyata tertunda. - Sekali habis, tidak bisa diulang. Generator yang sudah
done: truetetap done selamanya. Buat generator baru dengan memanggil fungsi generatornya lagi. yielddi arrow function tidak bisa.const f = *() => {}adalah syntax error. Generator harus function biasa (atau method class/shorthand object).- 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.
export default function App(): JSX.Element { return <h1>Hello world</h1> }
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>