1. Mengapa Array, String, Map, dan Set Semuanya Bisa Di-for...of?
Pernahkah Anda bertanya bagaimana satu sintaks for (const x of koleksi) dan operator spread [...koleksi] dapat bekerja pada struktur data yang berbeda-beda seperti Array, Map, Set, hingga NodeList?
Jawabannya adalah karena semuanya mengimplementasikan Iterable Protocol:
- Sebuah objek disebut Iterable jika ia memiliki metode dengan kunci simbol khusus
[Symbol.iterator](). - Ketika
for...ofdijalankan, JavaScript memanggil[Symbol.iterator]()tersebut untuk mendapatkan objek Iterator, yaitu objek yang memiliki metode.next()yang pada setiap langkahnya mengembalikan{ value: ..., done: false }hingga berakhir di{ value: undefined, done: true }!
2. Fungsi Generator (function* dan yield): Evaluasi Malas (Lazy Evaluation)
Membuat objek iterator secara manual dengan menulis metode .next() cukup bertele-tele. Sebagai gantinya, JavaScript menyediakan Generator Function (function*)!
Berbeda dengan fungsi biasa yang sekali dipanggil langsung berjalan sampai tuntas (return), fungsi generator (function*):
- Saat dipanggil, ia tidak langsung menjalankan kodenya, melainkan mengembalikan sebuah objek Generator (yang sekaligus merupakan Iterable dan Iterator).
- Setiap kali nilai berikutnya diminta (misalnya oleh putaran
for...ofatau.next()), fungsi berjalan sampai menemui kata kunciyield, menyerahkan nilai tersebut, lalu **membekukan posisinya (pause) tanpa memakan CPU** sampai nilai berikutnya diminta lagi!
// Menghasilkan urutan ID atau rentang angka secara malas (Lazy) —
// meskipun batasAkhir = 1_000_000_000, memori RAM yang terpakai hanya beberapa byte
// karena angka dibuat satu per satu saat dibutuhkan, BUKAN membuat Array 1 miliar elemen!
function* buatRentang(mulai, akhir, langkah = 1) {
for (let angka = mulai; angka <= akhir; angka += langkah) {
yield angka;
}
}
for (const n of buatRentang(10, 30, 10)) {
console.log(n); // Mencetak 10, lalu 20, lalu 30
}
Iterator Helpers Modern (Baseline Newly Available)
Dulu, untuk melakukan .map(), .filter(), atau mengambil 5 elemen pertama (.take(5)) pada hasil generator, kita terpaksa mengubahnya menjadi Array dulu dengan [...gen] (yang menghilangkan keunggulan hemat memorinya!).
Kini, Iterator.prototype telah dilengkapi metode Iterator Helpers yang bekerja secara malas (lazy):
iterator.map(fn)iterator.filter(fn)iterator.take(jumlahMaks)daniterator.drop(jumlahLewati)iterator.toArray()(mengubah hasil akhirnya saja menjadi Array).
function* bilanganAsliMulaiDari(awal = 1) {
let n = awal;
while (true) {
yield n++; // Perulangan tak terbatas yang 100% aman karena bersifat lazy!
}
}
// Mengambil 4 bilangan kuadrat genap pertama dari deret tak terbatas:
const empatKuadratGenap = bilanganAsliMulaiDari(1)
.filter((n) => n % 2 === 0)
.map((n) => n * n)
.take(4)
.toArray();
console.log(empatKuadratGenap); // [4, 16, 36, 64]
3. Async Generators (async function*) dan for await...of
Bagaimana jika setiap langkah di dalam deret data membutuhkan operasi asinkron—seperti mengambil halaman API yang dipaginasi (?page=1, ?page=2, ?page=3) satu per satu atau membaca aliran respons (ReadableStream)?
Gabungkan async dengan function* menjadi Async Generator (async function*), dan konsumsilah hasilnya dengan sangat bersih menggunakan for await (const item of ...):
// Mengambil data API berhalaman (paginated API) secara bertahap sesuai kebutuhan:
async function* ambilSemuaHalamanModul(urlDasar) {
let halaman = 1;
while (true) {
const res = await fetch(`${urlDasar}?page=${halaman}`);
if (!res.ok) throw new Error(`Gagal memuat halaman ${halaman}`);
const body = await res.json();
if (!body.data || body.data.length === 0) {
break; // Berhenti jika data sudah habis
}
// Serahkan potongan data halaman ini ke pemanggil
yield body.data;
if (!body.adaHalamanBerikutnya) break;
halaman++;
}
}
// Pemanggil cukup menggunakan 'for await...of' (dan bisa langsung 'break' kapan saja
// tanpa membuang kuota jaringan untuk halaman-halaman sisanya!):
async function cariModulPertamaYangCocok(kata) {
for await (const daftarModulHalamanIni of ambilSemuaHalamanModul("/api/modul")) {
const ketemu = daftarModulHalamanIni.find((m) => m.judul.includes(kata));
if (ketemu) {
return ketemu; // Begitu return/break, halaman API berikutnya tidak akan di-fetch!
}
}
return null;
}
Latihan Praktik: Membagi Pemrosesan Data Besar dengan Generator Chunking
Buatlah berkas generator-chunk.js dan jalankan dengan Node.js:
- Buat fungsi generator
function* bagiMenjadiPotongan(daftar, ukuranPotongan)yang menggunakanyield daftar.slice(i, i + ukuranPotongan)untuk memecah array besar menjadi kelompok-kelompok kecil tanpa menduplikasi seluruh array sekaligus. - Buat async generator
async function* prosesBatch(daftar, ukuran)yang memberi jeda simulasi50msper potongan, lalu konsumsi denganfor await...of.