Bab 11: Generator & Iterasi Lanjutan

3 menit baca

11.1 Generator

💡Analogi

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.

javascript
// 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

javascript
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:

  1. hitungMundur() → Bikin objek generator (kode belum jalan)
  2. .next() pertama → Jalan sampai yield 3, pause
  3. .next() kedua → Lanjut sampai yield 2, pause
  4. .next() ketiga → Lanjut sampai yield 1, pause
  5. .next() keempat → Lanjut sampai return, selesai (done: true)

Generator itu Iterable

Bisa pakai for...of:

javascript
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.

javascript
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

javascript
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:

javascript
// 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:

javascript
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:

javascript
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: 25

Alur:

  1. .next() pertama → Jalan sampai yield pertama, return pertanyaan
  2. .next("Budi") → "Budi" jadi hasil yield pertama, lanjut ke yield kedua
  3. .next(25) → 25 jadi hasil yield kedua, lanjut sampai return

generator.throw() — Lempar Error ke Generator

javascript
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

javascript
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?

  1. Data stream — Proses data besar satu per satu (gak muat di memori sekaligus)
  2. Lazy evaluation — Hitung nilai hanya kalau dibutuhkan
  3. Infinite sequence — Deret tak terbatas
javascript
// 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 batas
🎯Challenge

Buat generator fibonacci() yang menghasilkan deret Fibonacci tanpa batas:

javascript
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); // 8

11.2 Async Iteration & Async Generator

💡Analogi

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):

javascript
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:

javascript
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

FiturGeneratorAsync Generator
Deklarasifunction*async function*
YieldNilai biasaBisa await sebelum yield
Iterasifor...offor await...of
next() return{ value, done }Promise<{ value, done }>

Use Case Nyata: Pagination

javascript
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);
    }
  }
}
🎯Challenge

Buat async generator countdown(n) yang:

  1. Mulai dari n, hitung mundur sampai 0
  2. Setiap angka muncul setelah delay 1 detik
  3. Pakai for await...of untuk menampilkannya
javascript
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.