Kenapa ini penting
Kamu sudah sering memakai for...of untuk array dan string, serta spread [...x] untuk menyalin. Pernahkah kamu bertanya kenapa for...of bisa dipakai pada array, string, Map, dan Set, tapi tidak pada object biasa? Jawabannya adalah protokol iterasi: sebuah "kontrak" yang menentukan bagaimana sebuah struktur data menyerahkan isinya satu per satu.
Memahami protokol ini membuka kemampuan baru: membuat struktur data buatanmu sendiri yang bisa di-loop, membuat deret data yang dihitung saat dibutuhkan (lazy) sehingga hemat memori, dan bahkan deret tak terbatas seperti nomor antrean atau ID unik. Alat utamanya adalah generator function, yang juga menjadi dasar banyak pola lanjutan di JavaScript, termasuk aliran data asinkron yang akan kamu temui di materi async.
Konsep inti
Iterable dan iterator
Dua istilah ini sering tertukar:
- Iterable: object yang bisa di-iterasi, artinya punya method
[Symbol.iterator]()(ingat modul 5). Contoh bawaan: array, string,Map,Set,arguments,NodeListdi browser. - Iterator: object yang dikembalikan oleh method tersebut. Ia punya method
next()yang setiap kali dipanggil mengembalikan{ value, done }. Selamadonebernilaifalse,valueberisi data berikutnya. Saatdonebernilaitrue, iterasi selesai.
Analogi: iterable adalah buku, iterator adalah pembatas buku. Satu buku bisa dibaca beberapa orang dengan pembatasnya masing-masing. Setiap kali kamu memanggil next(), pembatas maju satu halaman.
Siapa yang memakai protokol ini
Semua fitur berikut bekerja pada iterable apa pun:
for...of- spread
[...iterable]danfn(...iterable) - destructuring array
const [a, b] = iterable Array.from(iterable)- constructor
new Map(iterable)dannew Set(iterable) Promise.all(iterable)dan sejenisnya
Object biasa {} bukan iterable. Untuk me-loop object, ubah dulu dengan Object.keys, Object.values, atau Object.entries, yang mengembalikan array (iterable).
for...of vs for...in
for...of |
for...in |
|
|---|---|---|
| Mengulang | nilai dari iterable | kunci (nama properti) dari object |
| Dipakai untuk | array, string, Map, Set | object biasa (jarang untuk array) |
| Ikut prototype? | tidak | ya, termasuk properti warisan yang enumerable |
Untuk array, selalu pakai for...of (atau method array). for...in pada array memberi indeks berupa string dan bisa ikut mengambil properti tambahan.
Generator function
Menulis iterator manual (dengan next() dan { value, done }) seperti di modul 5 itu repot. Generator function ditulis dengan function* dan memakai kata kunci yield untuk "menyerahkan" nilai satu per satu. Saat dipanggil, generator tidak langsung menjalankan isinya. Ia mengembalikan generator object yang sekaligus iterator dan iterable.
Setiap next() menjalankan kode sampai bertemu yield berikutnya, lalu berhenti sejenak (pause) dan mengingat posisinya. Panggilan next() berikutnya melanjutkan dari situ. Analogi: generator seperti mesin antrean bank. Mesin tidak mencetak 1.000 nomor sekaligus; ia mencetak satu nomor setiap kali tombol ditekan.
Hal penting tentang generator:
yield*mendelegasikan ke iterable lain (menyerahkan semua isinya).- Nilai
returndi generator menjadi{ value, done: true }dan tidak ikut muncul difor...ofatau spread. - Generator object hanya bisa dipakai sekali. Setelah habis, ia tetap habis. Panggil function generatornya lagi untuk mendapat iterator baru.
- Generator bisa tak terbatas (
while (true)) karena nilainya dihitung hanya saat diminta. Tapi jangan pernah spread generator tak terbatas, karena program akan berjalan selamanya.
Iterator helpers
Standar terbaru (ES2025) menambahkan method seperti .map(), .filter(), .take(n), .drop(n), dan .toArray() langsung pada iterator, sehingga kamu bisa mengolah deret lazy tanpa mengubahnya ke array dulu. Fitur ini tersedia di Node.js 22 ke atas dan browser modern. Untuk kode yang harus jalan di lingkungan lama, gunakan loop for...of biasa.
Contoh kode
// 1) Melihat protokol iterator secara manual
const huruf = ["a", "b"];
const it = huruf[Symbol.iterator]();
console.log(it.next()); // → { value: 'a', done: false }
console.log(it.next()); // → { value: 'b', done: false }
console.log(it.next()); // → { value: undefined, done: true }
// 2) Iterable bawaan
for (const ch of "Hai") console.log(ch);
// → H
// → a
// → i
const peta = new Map([["kopi", 18000], ["teh", 12000]]);
for (const [menu, harga] of peta) console.log(`${menu}=${harga}`);
// → kopi=18000
// → teh=12000
// 3) Object biasa bukan iterable
try {
for (const x of { a: 1 }) console.log(x);
} catch (err) {
console.log(err.constructor.name); // → TypeError
}
for (const [k, v] of Object.entries({ a: 1, b: 2 })) console.log(k, v);
// → a 1
// → b 2
// 4) for...of vs for...in pada array
const nilai = [10, 20];
for (const i in nilai) console.log(typeof i, i); // indeks berupa string
// → string 0
// → string 1
for (const v of nilai) console.log(v);
// → 10
// → 20
// 5) Generator dasar
function* hitungMundur(dari) {
while (dari > 0) {
yield dari;
dari--;
}
return "Selesai!"; // tidak ikut muncul di for...of
}
const gen = hitungMundur(3);
console.log(gen.next()); // → { value: 3, done: false }
console.log([...gen]); // → [ 2, 1 ] (lanjut dari posisi terakhir)
console.log(gen.next()); // → { value: undefined, done: true } (sudah habis)
console.log([...hitungMundur(3)]); // → [ 3, 2, 1 ] (iterator baru)
// 6) Generator membuat object buatan sendiri jadi iterable
class Playlist {
constructor(...lagu) {
this.lagu = lagu;
}
*[Symbol.iterator]() {
for (const judul of this.lagu) yield `♪ ${judul}`;
}
}
const pl = new Playlist("Bengawan Solo", "Rayuan Pulau Kelapa");
console.log([...pl]); // → [ '♪ Bengawan Solo', '♪ Rayuan Pulau Kelapa' ]
const [laguPertama] = pl;
console.log(laguPertama); // → ♪ Bengawan Solo
// 7) Generator tak terbatas (lazy): nomor antrean
function* nomorAntrean(awalan) {
let n = 1;
while (true) {
yield `${awalan}-${String(n).padStart(3, "0")}`;
n++;
}
}
const loket = nomorAntrean("A");
console.log(loket.next().value, loket.next().value); // → A-001 A-002
// Ambil 3 nomor berikutnya dengan aman (berhenti dengan break)
const ambil = [];
for (const nomor of loket) {
ambil.push(nomor);
if (ambil.length === 3) break;
}
console.log(ambil); // → [ 'A-003', 'A-004', 'A-005' ]
// 8) yield* untuk mendelegasikan
function* gabungan() {
yield "mulai";
yield* [1, 2];
yield* "ok";
}
console.log([...gabungan()]); // → [ 'mulai', 1, 2, 'o', 'k' ]
// 9) Spread pada iterable di berbagai tempat
const himpunan = new Set([5, 1, 9]);
console.log(Math.max(...himpunan)); // → 9
console.log(Array.from(hitungMundur(4), (n) => n * 10)); // → [ 40, 30, 20, 10 ]
// 10) Iterator helpers (ES2025, Node 22+): olah deret lazy tanpa array perantara
function* bilanganAsli() {
let n = 1;
while (true) yield n++;
}
const limaGenapPertama = bilanganAsli()
.filter((n) => n % 2 === 0)
.map((n) => n * n)
.take(5)
.toArray();
console.log(limaGenapPertama); // → [ 4, 16, 36, 64, 100 ]
Kesalahan umum
- Memakai
for...oflangsung pada object biasa. HasilnyaTypeError: ... is not iterable. PakaiObject.entries(obj)atauObject.keys(obj). - Memakai
for...inuntuk array. Indeksnya string dan bisa ikut properti warisan. Pakaifor...ofatau method array. - Memakai ulang generator object yang sudah habis. Iterator hanya sekali jalan. Panggil function generatornya lagi untuk mendapat iterator baru.
- Spread atau
Array.frompada generator tak terbatas. Program akan hang. Batasi denganbreak,take(n), atau kondisi berhenti. - Mengira nilai
returndi generator ikut di-loop.for...ofdan spread mengabaikannya; nilai itu hanya terlihat lewatnext()sebagai{ value, done: true }.
Latihan
- Mudah, Buat generator
genap(maks)yang menghasilkan bilangan genap dari 0 sampaimaks. Cetak hasilnya dengan spread untukmaks = 10. - Sedang, Buat generator
fibonacci()tak terbatas, lalu ambil 10 bilangan pertama memakaifor...ofdanbreak. - Menantang, Buat class
Rentangdengan constructor(awal, akhir, langkah = 1)yang bisa dipakai difor...of, spread, dan destructuring.[...new Rentang(1, 10, 3)]harus menghasilkan[1, 4, 7, 10].
Lihat solusi
// 1) Mudah
function* genap(maks) {
for (let i = 0; i <= maks; i += 2) yield i;
}
console.log([...genap(10)]); // → [ 0, 2, 4, 6, 8, 10 ]
// 2) Sedang
function* fibonacci() {
let [a, b] = [0, 1];
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
const sepuluh = [];
for (const f of fibonacci()) {
if (sepuluh.length === 10) break;
sepuluh.push(f);
}
console.log(sepuluh); // → [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
// 3) Menantang
class Rentang {
constructor(awal, akhir, langkah = 1) {
this.awal = awal;
this.akhir = akhir;
this.langkah = langkah;
}
*[Symbol.iterator]() {
for (let n = this.awal; n <= this.akhir; n += this.langkah) yield n;
}
}
console.log([...new Rentang(1, 10, 3)]); // → [ 1, 4, 7, 10 ]
const [x, y] = new Rentang(5, 8);
console.log(x, y); // → 5 6
Ringkasan
- Iterable adalah object yang punya
[Symbol.iterator](); iterator adalah object dengannext()yang mengembalikan{ value, done }. for...of, spread, destructuring,Array.from,Map, danSetsemuanya memakai protokol iterasi.for...ofmengulang nilai dari iterable;for...inmengulang kunci object dan sebaiknya tidak dipakai untuk array.- Generator
function*denganyieldmembuat iterator dengan mudah, berjalan lazy, dan bisa tak terbatas. - Generator object hanya sekali pakai; iterator helpers seperti
.take()dan.map()(ES2025) mengolah deret lazy secara langsung.