Iterables, iterator, dan generator function
Ribuan programmer mungkin tidak menyadari bahwa Array di Javascript bukanlah Array yang seutuhnya tanpa Symbol.iterator. Misal, kita punya array
1 |
const fibo = [1, 1, 2, 3, 5, 8, 13, 21] |
Lalu, dengan for...of...
, kita akan lihat value dari variable tersebut satu per satu.
1 2 |
for (let v of fibo) console.log(v) // 1 1 2 3 5 8 13 21 |
Nilai dari variable tersebut ditampilkan sesuai ekspektasi. Hal itu bisa terjadi karena Array yang kita kenal merupakan iterables
. Iterables
memiliki fungsi yang membuat nilai-nilai di data tersebut dapat dilihat secara berurutan. Bisa dengan menggunakan statemen for...of...
, bisa juga dengan spread operator (...)
.
Salah satu ciri dari iterables adalah memuat property [Symbol.iterator]
yang merupakan function
. Secara default property tersebut ada di variable yang memuat Array, String, Set, Map, atau TypedArray. Nah, apa yang terjadi kalau kita ubah Symbol.iterator
di itarables menjadi bukan function?
1 2 3 4 |
fibo[Symbol.iterator] = '' for (let v of fibo) console.log(v) // TypeError: fibo is not iterable |
Statement diatas akan melempar error karena for...of...
membutuhkan dan memanggil iterable property—Symbol.iterator—agar mendapatkan iterator
yang berfungsi untuk mengatur nilai-nilai yang akan diberikan ke variable v
.
Membuat Non-Iterable Object menjadi Iterable
Untuk membuat object biasa menjadi iterable, kita perlu menambahkan Symbol.iterator
ke dalam object tersebut. Symbol.iterator
harus berupa function yang mengembalikan iterator. Iterator
harus memuat function next()
yang akan dipanggil beberapa kali dan mengatur nilai yang akan dikeluarkan setiap pemanggilan function tersebut.
Misal, kita mempunyai object berupa data member yang dikelompokkan berdasarkan ranking.
1 2 3 4 5 6 7 8 9 10 11 12 |
const members = { beginner: [ { name: 'John', retired: true }, { name: 'Lucas'}, { name: 'Doni'} ], advance: [ { name: 'Toni' }, { name: 'Jessica', retired: true }, { name: 'Angelia' } ] } |
Lalu kita perlu membuat object tersebut menjadi iterables agar kita dapat menggunakan Spread Operator
untuk membuat array yang berisi semua member yang belum retired. Maka, kita bisa menambahkan Symbol.iterator
ke dalam object tersebut sebagai berikut
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
members[Symbol.iterator] = function () { const usersByRanks = Object.values(this) let rankIndex = 0 let userIndex = 0 return { next() { if (userIndex >= usersByRanks[rankIndex].length) { rankIndex++ userIndex = 0 } if (rankIndex >= usersByRanks.length) { return { done: true } } if (usersByRanks[rankIndex][userIndex].retired) { userIndex++ return this.next() } return { done: false, value: usersByRanks[rankIndex][userIndex++]} } } } |
Kini, setiap kali object members tersebut digunakan di for...of...
loop atau di spread operator
, variabel rankIndex
dan userIndex
di set menjadi 0 dan berfungsi sebagai “cursor” yang mengatur nilai mana yang akan dikeluarkan di setiap pemanggilan next().
1 2 |
console.log([...members]) // [ { name: 'Lucas' }, { name: 'Doni' }, { name: 'Toni' }, { name: 'Angelia' } ] |
Tanpa menggunakan spread operator
, kita juga bisa mengambil satu per satu nilai dari variable members
diatas dengan memanggil function next() melalui iteratornya.
1 2 3 4 5 6 7 |
const iterator = members[Symbol.iterator]() console.log(iterator.next()) // { done: false, value: { name: 'Lucas' } } console.log(iterator.next()) // { done: false, value: { name: 'Doni' } } console.log(iterator.next()) // { done: false, value: { name: 'Toni' } } |
Generator Function
Generator function adalah function yang menghasilkan iterator
. Ciri dari function ini adalah adanya tambahan asterik * pada setiap deklarasinya. Di generator function, kita juga menggunakan yield
untuk mengembalikan nilai yang dibutuhkan pada setiap iterasi. yield
hanya perlu mengembalikan nilai tanpa perlu menambahkan property done
.
Berikut contoh pernggunaan generator function menggantikan function iterator manual pada object member
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
members[Symbol.iterator] = function * () { const usersByRanks = Object.values(this) let rankIndex = 0 let userIndex = 0 while (true) { if (userIndex >= usersByRanks[rankIndex].length) { rankIndex++ userIndex = 0 } if (rankIndex >= usersByRanks.length) { break } if (usersByRanks[rankIndex][userIndex].retired) { userIndex++ continue } yield usersByRanks[rankIndex][userIndex++] // user } } |
Loop while
akan terus berputar sampai kondisi rankIndex >= usersByRanks.length
terpenuhi. Dan apabila kondisi usersByRanks[rankIndex][userIndex].retired
tidak terpenuhi, maka keyword yield
akan digunakan untuk mengeluarkan nilai user selanjutnya.
Selain membuat object menjadi iterables, generator function juga dapat digunakan secara mandiri untuk membuat sebuah iterator. Berikut adalah generator function yang menghasilkan iterator untuk bilangan fibonacci
1 2 3 4 5 6 7 8 9 10 11 12 |
function * fibo (maxCount = 1000) { let previous = 1 let initial = 0 while (maxCount-- > 0) { const nextValue = initial + previous yield nextValue previous = initial initial = nextValue } } console.log([...fibo(10)]) // [ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ] |
Generator function
diatas menerima satu argumen sebagai jumlah maksimal bilangan fibonacci yang akan dihasilkan.
Leave a Reply