kodingup.camp

Beranda/JavaScript Mastery: Object & Data

Aliran Data Iterable

Modul 13 dari 144 menit bacaIterable Data Flow

Isi modul

    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:

    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:

    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:

    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

    1. Memakai for...of langsung pada object biasa. Hasilnya TypeError: ... is not iterable. Pakai Object.entries(obj) atau Object.keys(obj).
    2. Memakai for...in untuk array. Indeksnya string dan bisa ikut properti warisan. Pakai for...of atau method array.
    3. Memakai ulang generator object yang sudah habis. Iterator hanya sekali jalan. Panggil function generatornya lagi untuk mendapat iterator baru.
    4. Spread atau Array.from pada generator tak terbatas. Program akan hang. Batasi dengan break, take(n), atau kondisi berhenti.
    5. Mengira nilai return di generator ikut di-loop. for...of dan spread mengabaikannya; nilai itu hanya terlihat lewat next() sebagai { value, done: true }.

    Latihan

    1. Mudah, Buat generator genap(maks) yang menghasilkan bilangan genap dari 0 sampai maks. Cetak hasilnya dengan spread untuk maks = 10.
    2. Sedang, Buat generator fibonacci() tak terbatas, lalu ambil 10 bilangan pertama memakai for...of dan break.
    3. Menantang, Buat class Rentang dengan constructor (awal, akhir, langkah = 1) yang bisa dipakai di for...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

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.