Belajar dengan membaca secara berurutan

Iterator dan Generator — Mengambil Value Satu per Satu

Iterator mengeluarkan value satu per satu, dan generator berhenti di setiap yield. Dibahas juga next(), for...of, spread syntax, dan deret tanpa akhir.

Saat membagikan nomor antrean satu per satu, atau menampilkan daftar ulasan yang panjang beberapa item sekaligus, kalau semua nomor atau halaman kamu buat di awal, kamu ikut menyiapkan bagian yang tidak pernah dipakai. Kamu memang bisa menulis fungsi yang mengingat value berikutnya dengan closure, tetapi kamu harus mencatat sendiri sudah sampai mana dan apakah prosesnya sudah selesai.

Artikel ini membahas iterator, yang mengembalikan value satu per satu, dan generator, yang memungkinkan kamu membuat iterator dalam bentuk fungsi.

Fungsi yang Melanjutkan dari Posisi Terakhir — Generator dan yield

Misalnya kamu ingin fungsi yang mengembalikan nomor panggilan loket satu per satu setiap kali dipanggil: 101, lalu 102, lalu 103. Fungsi biasa langsung selesai saat menjalankan return, dan pemanggilan berikutnya mulai lagi dari atas, jadi kamu butuh variabel di luar untuk mengingat sudah sampai nomor berapa.

Saat kamu memanggil generator function (fungsi yang dideklarasikan dengan function* dan bisa memakai yield di dalamnya), isi fungsinya belum berjalan. Yang dikembalikan adalah generator (object yang menjalankan isi fungsi sedikit demi sedikit). Setiap kali kamu memanggil next() miliknya, isi fungsi berjalan sampai yield berikutnya lalu berhenti di sana. next() mengembalikan object yang value-nya berisi value dari yield, dan done-nya menunjukkan apakah isi fungsi sudah selesai.

// Kalau dideklarasikan dengan function*, fungsinya menjadi generator function
function* callNumbers() {
  console.log("Loket dibuka");
  yield 101;
  yield 102;
  yield 103;
}

// Saat dipanggil, isi fungsi belum berjalan; yang dikembalikan hanya generator
const numbers = callNumbers();

// Setiap next() berjalan sampai yield berikutnya lalu berhenti
const first = numbers.next();                   // "Loket dibuka" ditampilkan di sini
console.log(`${first.value} / ${first.done}`);  // 101 / false
console.log(numbers.next().value);              // 102
console.log(numbers.next().value);              // 103

// Kalau tidak ada yield yang tersisa, done menjadi true
const last = numbers.next();
console.log(`${last.value} / ${last.done}`);    // undefined / true
Setiap next() Memajukan Posisi Berhenti
numbers =callNumbers()Belum ada satubaris pun berjalannext()pertamaMenampilkan tekslalu berhenti diyield 101next() keduadan ketigaBerhenti diyield 102 lalu 103next()keempatvalue: undefineddone: true
“Loket dibuka” ditampilkan pada next() pertama, lalu berhenti di yield 101. Setiap next() berjalan sampai yield berikutnya.

Setelah isi fungsi berjalan sampai akhir atau menjalankan return, done menjadi true, dan pemanggilan next() berikutnya tidak lagi menjalankan isi fungsi. Posisi terakhirnya dicatat oleh generator itu sendiri, jadi kamu tidak perlu variabel di luar untuk mengingat nomor panggilan.

yield Tidak Bisa Ditulis di Dalam Callback

yield hanya bisa ditulis di dalam kurung kurawal fungsi yang dideklarasikan dengan function*. Arrow function yang kamu berikan ke forEach adalah fungsi terpisah, jadi menulis yield di sana menghasilkan SyntaxError, dan pesan error-nya tidak menyebut yield sama sekali. Untuk mengembalikan element array satu per satu, tulis yield di dalam isi loop for atau for...of.

Terbitkan tiket antrean sebanyak yang sudah disiapkan. ticketCount sudah dideklarasikan.

① Jadikan issueTickets generator function yang mengembalikan 1 sampai count.

② Setelah semuanya dikembalikan, return “Penerbitan tiket ditutup”.

③ Berikan ticketCount dan terima generator-nya.

④ Ambil value 4 kali, lalu tampilkan value dan done dalam bentuk “1 / false”.

(Kalau berjalan dengan benar, penjelasan akan muncul.)

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Loop Tanpa Menulis next() — Iterator Protocol

Misalnya kamu ingin menampilkan status pengiriman secara berurutan: “Diterima”, “Dikirim”, “Sedang diantar”. Kalau kamu menulis satu pemanggilan next() untuk setiap tahap, kamu harus menambah baris setiap kali ada tahap baru. Kalau pakai loop while, kamu tetap harus menulis sendiri pengecekan done dan pemanggilan next() berikutnya.

Aturan bahwa setiap pemanggilan next() mengembalikan object berisi value dan done disebut iterator protocol, dan object yang mengikuti aturan itu disebut iterator. Generator dari bagian sebelumnya juga termasuk iterator. for...of terus memanggil next() sampai done bernilai true, dan memasukkan setiap value ke variabel loop.

function* deliverySteps() {
  yield "Diterima";
  yield "Dikirim";
  yield "Sedang diantar";
}

// for...of memanggil next() sampai done bernilai true
for (const step of deliverySteps()) {
  console.log(step);            // 3 baris: Diterima / Dikirim / Sedang diantar
}

// Perilaku yang sama, ditulis dengan while dan next()
const steps = deliverySteps();
let result = steps.next();
while (!result.done) {
  console.log(result.value);    // 3 baris: Diterima / Dikirim / Sedang diantar
  result = steps.next();
}
Yang Dikerjakan for...of untuk Kamu
Loop denganfor...ofMemanggil next()otomatisMengecek doneotomatisvalue masuk stepisi loop berjalanLoop sendiridengan whileresult =steps.next()Cek !result.donesendiriPakai result.valuedi isi loop
Keduanya mengulang kolom 2–4 sampai done bernilai true. Dengan for...of, kamu tidak perlu menulis next() dan pengecekan done.

Yang bisa kamu berikan ke for...of adalah value yang iterable (value yang iterator-nya bisa diambil oleh for...of), seperti array, Map, atau generator. for...of tidak membedakan jenis value-nya. for...of hanya memanggil next() pada iterator yang didapatnya, jadi semuanya bisa di-loop dengan cara yang sama. Tabel di bawah merangkum value yang masuk ke variabel loop.

Yang diberikan ke for...ofIsi variabel di setiap putaranCatatan
Array ["Diterima", "Dikirim"]Setiap element ("Diterima" → "Dikirim")Tiap loop membuat iterator baru dari element pertama
MapArray 2 element [key, value]Diambil sesuai urutan saat ditambahkan
GeneratorValue yang ditulis di yieldGenerator itu sendiri iterator-nya; lanjut dari posisi setelah next()
Object seperti { name: "Pulpen" }Tidak ada (TypeError: … is not iterable)Bukan iterable; untuk key pakai for...in atau Object.entries

Bagi ulasan produk menjadi beberapa halaman berisi 2 ulasan, lalu tampilkan. reviews sudah dideklarasikan.

① Di paginate, kembalikan array untuk setiap halaman secara berurutan dari awal.

② Ambil halaman-halaman dari reviews, 2 ulasan per halaman, satu per satu dengan loop.

③ Untuk setiap halaman, tampilkan “Halaman 1: ” diikuti nama pengulas yang digabung dengan “, ”.

④ Terakhir, tampilkan jumlah halaman dalam bentuk “Total 3 halaman”.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Mengubah Menjadi Array — Spread Syntax dan Sekali Pakai

Misalnya kamu ingin menampilkan jadwal penjemputan paket dalam satu baris, seperti “10:00 / 13:00 / 16:00”. Generator bukan array, jadi tidak punya join maupun length, dan kalau dibiarkan begitu saja, jumlah value-nya pun tidak bisa kamu hitung. Kalau setiap value kamu push ke array dengan for...of, kodenya bertambah beberapa baris hanya untuk itu.

Kalau kamu menaruh generator di dalam kurung siku array dengan spread syntax ..., value-nya diambil sampai done bernilai true, lalu disusun berurutan di array baru. Setelah kamu menulis const slotList = [...slots];, kamu bisa memakai join dan length.

function* pickupSlots() {
  yield "10:00";
  yield "13:00";
  yield "16:00";
}

// Generator bukan array, jadi tidak punya length
const slots = pickupSlots();
console.log(slots.length);                // undefined

// Spread syntax di dalam [] menyusun value-nya secara berurutan
const slotList = [...slots];
console.log(slotList.join(" / "));        // 10:00 / 13:00 / 16:00

// Kalau generator yang sudah dibaca habis di-spread lagi, hasilnya array kosong
console.log([...slots].length);           // 0

// Kalau fungsinya dipanggil lagi, kamu mendapat generator baru yang mulai dari awal
console.log([...pickupSlots()].length);   // 3
Spread Kedua Menghasilkan Array Kosong
[...slots]pertamaKetiga jadwal10:00 sampai 16:00[...slots]keduaArray kosonglength 0Dipanggil ulang[...pickupSlots()]Dibaca dari awallength 3
[...slots] kedua mulai membaca dari akhir, tempat spread pertama berhenti. Hanya generator yang dipanggil ulang yang mulai dari awal.

Kalau value yang sama kamu perlukan di dua tempat atau lebih, spread sekali ke array, simpan di variabel, lalu pakai ulang array itu. Generator yang sudah kamu loop sampai habis dengan for...of juga menghasilkan array kosong kalau di-spread sesudahnya.

Buat daftar nomor member dari pendaftaran member yang sudah disetujui. applications sudah dideklarasikan.

① Buat generator function yang hanya memilih pendaftaran yang disetujui dan mengembalikan nomor member-nya satu per satu.

② Terima generator-nya, ambil hanya nomor pertama, lalu tampilkan dalam bentuk “Pertama: M-4102”.

③ Kumpulkan sisanya ke dalam array, lalu tampilkan dalam bentuk “Sisa 2 nomor: M-4104, M-4105”.

④ Tampilkan semua nomor, termasuk yang pertama, digabung dengan “, ”.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output

Membuat Secukupnya Saja — Lazy Evaluation dan Deret Tanpa Akhir

Saat memberi nomor urut pada pengumuman mulai dari 1, kamu tidak tahu dari awal akan ada berapa pengumuman. Kalau kamu membuat 1000 nomor di array lebih dulu, sisa nomornya tetap dihitung walaupun yang dipakai hanya 3. Kalau batas atasnya tidak ditentukan, array-nya bahkan tidak akan pernah selesai dibuat.

Dengan generator, kamu bisa menulis lazy evaluation (cara menghitung value baru pada saat value itu dibutuhkan). Fungsi yang terus melakukan yield tanpa henti dengan while (true) pun hanya menjalankan isinya sejauh next() dipanggil. Destructuring array juga bisa dipakai pada generator, dan mengambil value sebanyak variabel yang kamu tulis di sisi kiri.

function* serialFrom(start) {
  let number = start;
  while (true) {
    console.log(`Nomor ${number} dibuat`);
    yield number;
    number++;
  }
}

// Destructuring mengambil value sebanyak variabel di sisi kiri
const [first, second] = serialFrom(1);   // Nomor 1 dibuat / Nomor 2 dibuat
console.log(`${first}, ${second}`);       // 1, 2

// for...of berhenti mengambil value begitu keluar dengan break
for (const number of serialFrom(1)) {
  console.log(`Pengumuman No.${number}`);
  if (number === 3) break;
}
// Nomor 1 dibuat / Pengumuman No.1 / Nomor 2 dibuat / Pengumuman No.2
// Nomor 3 dibuat / Pengumuman No.3 (nomor 4 tidak pernah dibuat)
Cara Mengambil Menentukan Berapa Kali next() Dipanggil
serialFrom(1)nomor tanpa akhirDiterima denganconst [a, b] =for...of cetak 3lalu break[...serialFrom(1)]jadi arraynext() dipanggil2 kalinext() dipanggil3 kalidone tidak pernahbernilai trueHanya membuat 1dan 2 lalu selesaiMembuat 1 sampai 3lalu keluar loopTerus memanggilnext() tanpa henti
Dua kolom di kiri berhenti karena jumlah value yang diambil sudah ditentukan. Spread syntax membaca sampai done bernilai true, jadi tidak bisa dipakai pada deret tanpa akhir.

Untuk mengubah deret tanpa akhir menjadi array, sisipkan generator lain di tengahnya yang mengambil value dalam jumlah tertentu lalu melakukan return. Begitu generator di tengah itu selesai, done menjadi true, sehingga spread syntax juga berhenti di situ.

Kalau yield Tidak Pernah Tercapai, next() Tidak Pernah Selesai

Kalau kamu menulis loop while (true) yang hanya melakukan yield saat kondisi if terpenuhi, dan kondisinya tidak pernah terpenuhi, next() tidak akan pernah selesai. Eksekusinya tidak selesai, dan baris di bawahnya tidak pernah berjalan. Kalau membuat pengulangan tanpa akhir, pastikan dulu bahwa value yang memenuhi kondisi pasti akan muncul suatu saat.

Tentukan waktu tunggu sebelum mencoba ulang setelah request jaringan gagal, dengan menggandakannya setiap kali. retryDelays sudah dideklarasikan dan mengembalikan value tanpa henti.

① Buat take supaya hanya mengembalikan count value pertama dari source, lalu selesai.

② Mulai dari 100, masukkan 4 value pertama ke array, lalu tampilkan dengan digabung memakai “ → ”.

③ Ambil 5 value pertama dengan loop, lalu tampilkan jumlahnya dalam bentuk “Total: 3100ms”.

④ Berikan 0 sebagai jumlah, ubah hasilnya menjadi array, lalu tampilkan panjangnya.

JavaScript / TypeScript Editor

Jalankan kode untuk melihat output
QUIZ

Cek Pemahaman

Jawab setiap pertanyaan satu per satu.

Soal 1Tepat setelah kamu memanggil fungsi yang dideklarasikan dengan function* dan menerima generator, sampai mana isi fungsinya sudah berjalan?

Soal 2Kalau kamu menjalankan [...slots] dua kali berturut-turut pada generator slots yang melakukan yield 3 value, berapa panjang hasil yang kedua?

Soal 3Untuk generator gen yang terus melakukan yield tanpa henti dengan while (true), cara mana yang prosesnya tidak pernah selesai?