Selanjutnya, pembaca ditawari artikel, yang, dalam kasus tanggapan positif, dapat berkembang menjadi sebuah siklus. Jika saya berhasil menulis siklus ini, dan Pembaca berhasil menguasainya, akan jelas tentang kode berikut tidak hanya apa yang dilakukannya, tetapi juga cara kerjanya di balik terpal:
while (true) {
const data = yield getNextChunk(); //
const processed = processData(data);
try {
yield sendProcessedData(processed);
showOkResult();
} catch (err) {
showError();
}
}
Ini adalah bagian percobaan pertama: Iterator dan Generator.
Iterator
Jadi, iterator adalah antarmuka yang menyediakan akses berurutan ke data.
Seperti yang Anda lihat, definisi tidak mengatakan apa-apa tentang data atau struktur memori. Memang, urutan undefined dapat direpresentasikan sebagai iterator tanpa menghabiskan ruang memori.
Saya menyarankan pembaca untuk menjawab pertanyaan: apakah array merupakan iterator?
Menjawab
. shift pop .
Lalu, mengapa iterator diperlukan jika sebuah array, salah satu struktur dasar bahasa, memungkinkan Anda untuk bekerja dengan data baik secara berurutan maupun dalam urutan arbitrer?
Mari kita bayangkan bahwa kita membutuhkan iterator yang mengimplementasikan deret bilangan asli. Atau angka Fibonacci. Atau urutan tak berujung lainnya . Sulit untuk menempatkan urutan tanpa akhir dalam sebuah array; Anda memerlukan mekanisme untuk mengisi array secara bertahap dengan data, serta menghapus data lama agar tidak mengisi seluruh memori proses. Ini adalah komplikasi yang tidak perlu, yang disertai dengan kompleksitas tambahan dari implementasi dan dukungan, terlepas dari fakta bahwa solusi tanpa array dapat masuk ke dalam beberapa baris:
const getNaturalRow = () => {
let current = 0;
return () => ++current;
};
Selain itu, iterator bisa mewakili penerimaan data dari saluran eksternal, misalnya websocket.
Dalam javascript, iterator adalah objek apa pun yang memiliki metode next () yang mengembalikan struktur dengan nilai bidang - nilai saat ini dari iterator dan selesai - bendera yang menunjukkan akhir urutan (konvensi ini dijelaskan dalam standar bahasa ECMAScript ). Objek seperti itu mengimplementasikan antarmuka Iterator. Mari tulis ulang contoh sebelumnya dalam format ini:
const getNaturalRow = () => ({
_current: 0,
next() { return {
value: ++this._current,
done: false,
}},
});
Javascript juga memiliki antarmuka Iterable, yaitu objek yang memiliki metode @@ iterator (konstanta ini tersedia sebagai Symbol.iterator) yang mengembalikan iterator. Untuk objek yang mengimplementasikan antarmuka seperti itu, tersedia traversal operator
for..of. Mari kita tulis ulang contoh kita sekali lagi, hanya kali ini sebagai implementasi Iterable:
const naturalRowIterator = {
[Symbol.iterator]: () => ({
_current: 0,
next() { return {
value: ++this._current,
done: this._current > 3,
}},
}),
}
for (num of naturalRowIterator) {
console.log(num);
}
// : 1, 2, 3
Seperti yang Anda lihat, kami harus membuat flag done di beberapa titik menjadi positif, jika tidak loop akan menjadi tak terbatas.
Generator
Generator menjadi tahap selanjutnya dalam evolusi iterator. Mereka menyediakan gula sintaksis untuk mengembalikan nilai iterator seperti nilai fungsi. Generator adalah fungsi (dideklarasikan dengan asterisk: function * ) yang mengembalikan iterator. Dalam kasus ini, iterator tidak dikembalikan secara eksplisit, fungsinya hanya mengembalikan nilai dari iterator menggunakan pernyataan yield . Saat fungsi menyelesaikan eksekusinya, iterator dianggap selesai (hasil panggilan berikutnya ke metode berikutnya akan memiliki tanda done sama dengan true)
function* naturalRowGenerator() {
let current = 1;
while (current <= 3) {
yield current;
current++;
}
}
for (num of naturalRowGenerator()) {
console.log(num);
}
// : 1, 2, 3
Dalam contoh sederhana ini, nuansa utama generator terlihat dengan mata telanjang: kode di dalam fungsi generator tidak dijalankan secara serempak . Kode generator dijalankan secara bertahap, sebagai hasil dari panggilan ke next () pada iterator terkait. Mari kita lihat bagaimana kode generator dijalankan pada contoh sebelumnya. Kami akan menggunakan kursor khusus untuk menandai di mana generator berhenti.
Saat naturalRowGenerator dipanggil, iterator dibuat.
function* naturalRowGenerator() {
▷let current = 1;
while (current <= 3) {
yield current;
current++;
}
}
Selanjutnya, ketika kita memanggil metode selanjutnya untuk tiga kali pertama, atau, dalam kasus kita, kita mengulang melalui loop, kursor diposisikan setelah pernyataan yield.
function* naturalRowGenerator() {
let current = 1;
while (current <= 3) {
yield current; ▷
current++;
}
}
Dan untuk semua panggilan berikutnya ke next dan setelah keluar dari loop, generator mengakhiri eksekusinya dan, hasil dari panggilan next akan menjadi
{ value: undefined, done: true }
Meneruskan parameter ke iterator
Bayangkan kita perlu menambahkan kemampuan untuk mengatur ulang penghitung saat ini dan mulai menghitung dari awal ke iterator bilangan asli kita.
naturalRowIterator.next() // 1
naturalRowIterator.next() // 2
naturalRowIterator.next(true) // 1
naturalRowIterator.next() // 2
Jelas bagaimana menangani parameter seperti itu dalam iterator yang ditulis sendiri, tetapi bagaimana dengan generator?
Ternyata generator mendukung penyaluran parameter!
function* naturalRowGenerator() {
let current = 1;
while (true) {
const reset = yield current;
if (reset) {
current = 1;
} else {
current++;
}
}
}
Parameter yang diteruskan tersedia sebagai hasil dari pernyataan hasil. Mari kita coba menambahkan kejelasan dengan pendekatan kursor. Saat iterator dibuat, tidak ada yang berubah. Ini diikuti dengan panggilan pertama ke metode next ():
function* naturalRowGenerator() {
let current = 1;
while (true) {
const reset = ▷yield current;
if (reset) {
current = 1;
} else {
current++;
}
}
}
Kursor membeku pada saat kembali dari pernyataan hasil. Pada panggilan berikutnya ke berikutnya, nilai yang diteruskan ke fungsi tersebut akan menetapkan nilai variabel reset. Di manakah nilai yang diteruskan ke panggilan pertama ke panggilan berikutnya berakhir, karena belum ada panggilan untuk menyerah? Tidak kemana-mana! Ini akan larut dalam luasnya pengumpul sampah. Jika Anda perlu meneruskan beberapa nilai awal ke generator, maka ini bisa dilakukan dengan menggunakan argumen generator itu sendiri. Contoh:
function* naturalRowGenerator(start = 1) {
let current = start;
while (true) {
const reset = yield current;
if (reset) {
current = start;
} else {
current++;
}
}
}
const iterator = naturalRowGenerator(10);
iterator.next() // 10
iterator.next() // 11
iterator.next(true) // 10
Kesimpulan
Kami telah membahas konsep iterator dan implementasinya dalam bahasa javascript. Kami juga mempelajari generator - konstruksi sintaksis untuk mengimplementasikan iterator dengan mudah.
Meskipun saya telah memberikan contoh urutan angka dalam artikel ini, iterator JavaScript dapat melakukan lebih banyak lagi. Mereka dapat mewakili urutan data apa pun dan bahkan banyak mesin negara hingga. Pada artikel berikutnya, saya ingin berbicara tentang bagaimana Anda dapat menggunakan generator untuk membangun proses asinkron (coroutines, goroutines, csp, dll.).