Soal 1Tepat setelah kamu memanggil fungsi yang dideklarasikan dengan function* dan menerima generator, sampai mana isi fungsinya sudah berjalan?
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
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.
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 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...of | Isi variabel di setiap putaran | Catatan |
|---|---|---|
| Array ["Diterima", "Dikirim"] | Setiap element ("Diterima" → "Dikirim") | Tiap loop membuat iterator baru dari element pertama |
| Map | Array 2 element [key, value] | Diambil sesuai urutan saat ditambahkan |
| Generator | Value yang ditulis di yield | Generator 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 |
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
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.
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)
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.
Cek Pemahaman
Jawab setiap pertanyaan satu per satu.
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?