Bab 11: Generator & Iterasi Lanjutan
11.1 Generator
Bayangin kamu baca buku. Fungsi biasa itu kayak kamu baca seluruh buku sekaligus dan langsung kasih ringkasannya. Generator itu kayak kamu baca satu halaman, kasih hasilnya, lalu pause — baru lanjut baca halaman berikutnya kalau diminta.
Apa itu Generator?
Fungsi biasa: return satu nilai, selesai. Generator: bisa yield (menghasilkan) banyak nilai, satu per satu, sesuai permintaan.
// Fungsi biasa — return sekali, selesai
function biasa() {
return 1;
return 2; // Gak pernah sampai sini
}
// Generator — yield berkali-kali
function* generator() {
yield 1;
yield 2;
yield 3;
}Perhatikan tanda * setelah function — itu yang bikin dia jadi generator.
Cara Kerja
function* hitungMundur() {
yield 3;
yield 2;
yield 1;
return "Selesai!";
}
// Panggil generator — BELUM jalan, cuma bikin "objek generator"
let gen = hitungMundur();
// Panggil .next() untuk jalankan sampai yield berikutnya
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: "Selesai!", done: true }
console.log(gen.next()); // { value: undefined, done: true }Alurnya:
hitungMundur()→ Bikin objek generator (kode belum jalan).next()pertama → Jalan sampaiyield 3, pause.next()kedua → Lanjut sampaiyield 2, pause.next()ketiga → Lanjut sampaiyield 1, pause.next()keempat → Lanjut sampaireturn, selesai (done: true)
Generator itu Iterable
Bisa pakai for...of:
function* buahGenerator() {
yield "Apel";
yield "Mangga";
yield "Jeruk";
}
for (let buah of buahGenerator()) {
console.log(buah);
}
// "Apel"
// "Mangga"
// "Jeruk"⚠️ Jebakan: for...of mengabaikan nilai dari return. Kalau mau semua nilai muncul, pakai yield (bukan return) untuk nilai terakhir.
function* gen() {
yield 1;
yield 2;
return 3; // INI GAK MUNCUL di for...of!
}
for (let val of gen()) {
console.log(val); // 1, 2 (gak ada 3!)
}Spread dengan Generator
function* angka() {
yield 1;
yield 2;
yield 3;
}
let arr = [0, ...angka()]; // [0, 1, 2, 3]
console.log(arr);Generator sebagai Iterator
Bikin objek iterable jadi lebih ringkas:
// Tanpa generator (verbose)
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return {
current: this.from,
last: this.to,
next() {
if (this.current <= this.last) {
return { done: false, value: this.current++ };
}
return { done: true };
}
};
}
};
// Dengan generator (ringkas!)
let rangeGen = {
from: 1,
to: 5,
*[Symbol.iterator]() {
for (let i = this.from; i <= this.to; i++) {
yield i;
}
}
};
console.log([...rangeGen]); // [1, 2, 3, 4, 5]Generator Composition (yield*)
Menyisipkan generator di dalam generator:
function* angka(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* alfanumerik() {
yield* angka(48, 57); // Karakter 0-9
yield* angka(65, 90); // Karakter A-Z
yield* angka(97, 122); // Karakter a-z
}
let str = '';
for (let code of alfanumerik()) {
str += String.fromCharCode(code);
}
console.log(str); // "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz"yield* itu kayak bilang: "Serahin ke generator lain dulu, baru balik ke sini."
yield Dua Arah — Kirim Nilai ke Generator
yield bukan cuma mengeluarkan nilai — bisa juga menerima nilai dari luar:
function* tanyaJawab() {
let jawaban1 = yield "Siapa namamu?";
console.log("Nama:", jawaban1);
let jawaban2 = yield "Berapa umurmu?";
console.log("Umur:", jawaban2);
return "Selesai!";
}
let gen = tanyaJawab();
console.log(gen.next().value); // "Siapa namamu?"
console.log(gen.next("Budi").value); // "Berapa umurmu?" (Budi masuk ke jawaban1)
console.log(gen.next(25).done); // true (25 masuk ke jawaban2)
// Output:
// "Siapa namamu?"
// Nama: Budi
// "Berapa umurmu?"
// Umur: 25Alur:
.next()pertama → Jalan sampai yield pertama, return pertanyaan.next("Budi")→ "Budi" jadi hasil yield pertama, lanjut ke yield kedua.next(25)→ 25 jadi hasil yield kedua, lanjut sampai return
generator.throw() — Lempar Error ke Generator
function* gen() {
try {
let hasil = yield "Minta data...";
console.log("Dapat:", hasil);
} catch (err) {
console.log("Error:", err.message);
}
}
let g = gen();
console.log(g.next().value); // "Minta data..."
// Lempar error ke dalam generator
g.throw(new Error("Koneksi gagal"));
// Output: "Error: Koneksi gagal"generator.return() — Paksa Selesai
function* gen() {
yield 1;
yield 2;
yield 3;
}
let g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.return("Stop")); // { value: "Stop", done: true }
console.log(g.next()); // { value: undefined, done: true }Kapan Pakai Generator?
- Data stream — Proses data besar satu per satu (gak muat di memori sekaligus)
- Lazy evaluation — Hitung nilai hanya kalau dibutuhkan
- Infinite sequence — Deret tak terbatas
// Infinite ID generator
function* idGenerator() {
let id = 1;
while (true) {
yield id++;
}
}
let genId = idGenerator();
console.log(genId.next().value); // 1
console.log(genId.next().value); // 2
console.log(genId.next().value); // 3
// ...bisa terus tanpa batasBuat generator fibonacci() yang menghasilkan deret Fibonacci tanpa batas:
function* fibonacci() {
// Kode kamu di sini
}
// Test:
let fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2
console.log(fib.next().value); // 3
console.log(fib.next().value); // 5
console.log(fib.next().value); // 811.2 Async Iteration & Async Generator
Generator biasa itu kayak baca buku halaman per halaman. Async generator itu kayak nonton serial di streaming — setiap episode butuh waktu loading, tapi kamu bisa nonton satu per satu begitu siap.
Async Iterable
Untuk data yang datang secara asinkron (dari server, database, dll):
let asyncRange = {
from: 1,
to: 5,
// Pakai Symbol.asyncIterator (bukan Symbol.iterator)
[Symbol.asyncIterator]() {
return {
current: this.from,
last: this.to,
// next() harus return Promise
async next() {
// Simulasi delay (misal ambil dari server)
await new Promise(resolve => setTimeout(resolve, 500));
if (this.current <= this.last) {
return { done: false, value: this.current++ };
}
return { done: true };
}
};
}
};
// Pakai for await...of
(async () => {
for await (let value of asyncRange) {
console.log(value); // 1, 2, 3, 4, 5 (setiap 500ms)
}
})();Async Generator
Lebih ringkas dari async iterable manual:
async function* ambilHalaman(url) {
let halaman = 1;
while (true) {
// Simulasi fetch per halaman
await new Promise(resolve => setTimeout(resolve, 500));
let data = { halaman, items: [`Item ${halaman}a`, `Item ${halaman}b`] };
if (halaman > 3) return; // Stop setelah 3 halaman
yield data;
halaman++;
}
}
// Pakai
(async () => {
for await (let data of ambilHalaman("/api/produk")) {
console.log(`Halaman ${data.halaman}:`, data.items);
}
})();
// Halaman 1: ["Item 1a", "Item 1b"]
// Halaman 2: ["Item 2a", "Item 2b"]
// Halaman 3: ["Item 3a", "Item 3b"]Perbedaan Generator vs Async Generator
| Fitur | Generator | Async Generator |
|---|---|---|
| Deklarasi | function* | async function* |
| Yield | Nilai biasa | Bisa await sebelum yield |
| Iterasi | for...of | for await...of |
| next() return | { value, done } | Promise<{ value, done }> |
Use Case Nyata: Pagination
async function* fetchPaginated(baseUrl) {
let page = 1;
let hasMore = true;
while (hasMore) {
let response = await fetch(`${baseUrl}?page=${page}`);
let data = await response.json();
yield data.items;
hasMore = data.hasNextPage;
page++;
}
}
// Pakai
async function prosesSemuaData() {
for await (let items of fetchPaginated("/api/users")) {
for (let item of items) {
console.log("User:", item.nama);
}
}
}Buat async generator countdown(n) yang:
- Mulai dari
n, hitung mundur sampai 0 - Setiap angka muncul setelah delay 1 detik
- Pakai
for await...ofuntuk menampilkannya
async function* countdown(n) {
// Kode kamu di sini
}
// Test:
(async () => {
for await (let angka of countdown(5)) {
console.log(angka); // 5, 4, 3, 2, 1, 0 (setiap 1 detik)
}
console.log("Selesai!");
})();Sudah paham materi ini?
Tandai sebagai selesai untuk melacak progress-mu.