Pendalaman tentang fungsi generator asinkron di JavaScript, menjelajahi protokol iterasi asinkron, kasus penggunaan, dan contoh praktis untuk pengembangan web modern.
Fungsi Generator Asinkron: Menguasai Protokol Iterasi Asinkron
Pemrograman asinkron adalah landasan pengembangan JavaScript modern, terutama ketika berurusan dengan operasi I/O seperti mengambil data dari API, membaca file, atau berinteraksi dengan database. Secara tradisional, kami mengandalkan Promises dan async/await untuk mengelola tugas-tugas asinkron ini. Namun, fungsi generator asinkron menawarkan cara yang kuat dan elegan untuk menangani iterasi asinkron, memungkinkan kita memproses aliran data secara asinkron dan efisien.
Memahami Protokol Iterasi Asinkron
Sebelum menyelami fungsi generator asinkron, penting untuk memahami protokol iterasi asinkron yang menjadi dasarnya. Protokol ini mendefinisikan bagaimana sumber data asinkron dapat diiterasi secara terkontrol dan dapat diprediksi.
Protokol Iterable Asinkron
Protokol iterable asinkron mendefinisikan objek yang dapat diiterasi secara asinkron. Sebuah objek sesuai dengan protokol ini jika ia memiliki metode yang dikunci oleh Symbol.asyncIterator
yang mengembalikan iterator asinkron.
Bayangkan iterable seperti daftar putar lagu. Iterable asinkron seperti daftar putar di mana setiap lagu perlu dimuat (secara asinkron) sebelum dapat diputar.
Contoh:
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
next() {
// Secara asinkron ambil nilai berikutnya
}
};
}
};
Protokol Iterator Asinkron
Protokol iterator asinkron mendefinisikan metode yang harus diimplementasikan oleh iterator asinkron. Sebuah objek yang sesuai dengan protokol ini harus memiliki metode next()
, dan opsional metode return()
dan throw()
.
- next(): Metode ini mengembalikan Promise yang menghasilkan objek dengan dua properti:
value
dandone
.value
berisi nilai berikutnya dalam urutan, dandone
adalah boolean yang menunjukkan apakah iterasi telah selesai. - return(): (Opsional) Metode ini mengembalikan Promise yang menghasilkan objek dengan properti
value
dandone
. Ini menandakan bahwa iterator sedang ditutup. Ini berguna untuk melepaskan sumber daya. - throw(): (Opsional) Metode ini mengembalikan Promise yang ditolak dengan kesalahan. Ini digunakan untuk menandakan bahwa terjadi kesalahan selama iterasi.
Contoh:
const asyncIterator = {
next() {
return new Promise((resolve) => {
// Secara asinkron ambil nilai berikutnya
setTimeout(() => {
resolve({ value: /* some value */, done: false });
}, 100);
});
},
return() {
return Promise.resolve({ value: undefined, done: true });
},
throw(error) {
return Promise.reject(error);
}
};
Memperkenalkan Fungsi Generator Asinkron
Fungsi generator asinkron menyediakan cara yang lebih nyaman dan mudah dibaca untuk membuat iterator dan iterable asinkron. Mereka menggabungkan kekuatan generator dengan asinkronisitas Promises.
Sintaks
Fungsi generator asinkron dideklarasikan menggunakan sintaks async function*
:
async function* myAsyncGenerator() {
// Operasi asinkron dan pernyataan yield di sini
}
Kata Kunci yield
Di dalam fungsi generator asinkron, kata kunci yield
digunakan untuk menghasilkan nilai secara asinkron. Setiap pernyataan yield
secara efektif menjeda eksekusi fungsi generator sampai Promise yang dihasilkan diselesaikan.
Contoh:
async function* fetchUsers() {
const user1 = await fetch('https://example.com/api/users/1').then(res => res.json());
yield user1;
const user2 = await fetch('https://example.com/api/users/2').then(res => res.json());
yield user2;
const user3 = await fetch('https://example.com/api/users/3').then(res => res.json());
yield user3;
}
Menggunakan Generator Asinkron dengan for await...of
Anda dapat melakukan iterasi melalui nilai-nilai yang dihasilkan oleh fungsi generator asinkron menggunakan loop for await...of
. Loop ini secara otomatis menangani resolusi asinkron dari Promises yang dihasilkan oleh generator.
Contoh:
async function main() {
for await (const user of fetchUsers()) {
console.log(user);
}
}
main();
Kasus Penggunaan Praktis untuk Fungsi Generator Asinkron
Fungsi generator asinkron unggul dalam skenario yang melibatkan aliran data asinkron, seperti:
1. Streaming Data dari API
Bayangkan mengambil dataset besar dari API yang mendukung penomoran halaman. Alih-alih mengambil seluruh dataset sekaligus, Anda dapat menggunakan fungsi generator asinkron untuk mengambil dan menghasilkan halaman data secara bertahap.
Contoh (Mengambil Data yang Dipaginasi):
async function* fetchPaginatedData(url, pageSize = 10) {
let page = 1;
while (true) {
const response = await fetch(`${url}?page=${page}&pageSize=${pageSize}`);
const data = await response.json();
if (data.length === 0) {
return; // Tidak ada lagi data
}
for (const item of data) {
yield item;
}
page++;
}
}
async function main() {
for await (const item of fetchPaginatedData('https://api.example.com/data')) {
console.log(item);
}
}
main();
Contoh Internasional (API Nilai Tukar Mata Uang):
async function* fetchExchangeRates(currencyPair, startDate, endDate) {
let currentDate = new Date(startDate);
while (currentDate <= new Date(endDate)) {
const dateString = currentDate.toISOString().split('T')[0]; // YYYY-MM-DD
const url = `https://api.exchangerate.host/${dateString}?base=${currencyPair.substring(0,3)}&symbols=${currencyPair.substring(3,6)}`;
try {
const response = await fetch(url);
const data = await response.json();
if (data.success) {
yield {
date: dateString,
rate: data.rates[currencyPair.substring(3,6)],
};
}
} catch (error) {
console.error(`Error fetching data for ${dateString}:`, error);
// Anda mungkin ingin menangani kesalahan secara berbeda, misalnya, mencoba lagi atau melewati tanggal.
}
currentDate.setDate(currentDate.getDate() + 1);
}
}
async function main() {
const currencyPair = 'EURUSD';
const startDate = '2023-01-01';
const endDate = '2023-01-10';
for await (const rate of fetchExchangeRates(currencyPair, startDate, endDate)) {
console.log(rate);
}
}
main();
Contoh ini mengambil nilai tukar harian EUR ke USD untuk rentang tanggal yang diberikan. Ini menangani potensi kesalahan selama panggilan API. Ingatlah untuk mengganti `https://api.exchangerate.host` dengan titik akhir API yang andal dan sesuai.
2. Memproses File Besar
Saat bekerja dengan file besar, membaca seluruh file ke dalam memori bisa menjadi tidak efisien. Fungsi generator asinkron memungkinkan Anda membaca file baris demi baris atau dalam potongan, memproses setiap potongan secara asinkron.
Contoh (Membaca File Besar Baris demi Baris - Node.js):
const fs = require('fs');
const readline = require('readline');
async function* readLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
async function main() {
for await (const line of readLines('large_file.txt')) {
// Proses setiap baris secara asinkron
console.log(line);
}
}
main();
Contoh Node.js ini menunjukkan cara membaca file baris demi baris menggunakan fs.createReadStream
dan readline.createInterface
. Fungsi generator asinkron readLines
menghasilkan setiap baris secara asinkron.
3. Menangani Aliran Data Real-Time (WebSocket, Event yang Dikirim Server)
Fungsi generator asinkron sangat cocok untuk memproses aliran data real-time dari sumber seperti WebSocket atau Server-Sent Events (SSE). Anda dapat terus menghasilkan data saat tiba dari aliran.
Contoh (Memproses Data dari WebSocket - Konseptual):
// Ini adalah contoh konseptual dan membutuhkan pustaka WebSocket seperti 'ws' (Node.js) atau API WebSocket bawaan browser.
async function* processWebSocketStream(url) {
const websocket = new WebSocket(url);
websocket.onmessage = (event) => {
//Ini perlu ditangani di luar generator.
//Biasanya, Anda akan mendorong event.data ke dalam antrian
//dan generator akan secara asinkron menarik dari antrian
//melalui Promise yang diselesaikan ketika data tersedia.
};
websocket.onerror = (error) => {
//Tangani kesalahan.
};
websocket.onclose = () => {
//Tangani penutupan.
}
//Penyelesaian dan pengelolaan antrian yang sebenarnya akan terjadi di sini,
//memanfaatkan Promises untuk melakukan sinkronisasi antara websocket.onmessage
//event dan fungsi generator asinkron.
//Ini adalah ilustrasi yang disederhanakan.
//while(true){ //Gunakan ini jika mengantri event dengan benar.
// const data = await new Promise((resolve) => {
// // Selesaikan promise ketika data tersedia di antrian.
// })
// yield data
//}
}
async function main() {
// for await (const message of processWebSocketStream('wss://example.com/ws')) {
// console.log(message);
// }
console.log("Contoh WebSocket - hanya konseptual. Lihat komentar dalam kode untuk detailnya.");
}
main();
Catatan Penting tentang contoh WebSocket:
- Contoh WebSocket yang diberikan terutama bersifat konseptual karena mengintegrasikan langsung sifat berbasis event WebSocket dengan generator asinkron memerlukan sinkronisasi yang cermat menggunakan Promises dan antrian.
- Implementasi dunia nyata biasanya melibatkan buffering pesan WebSocket yang masuk dalam antrian dan menggunakan Promise untuk memberi sinyal kepada generator asinkron ketika data baru tersedia. Ini memastikan bahwa generator tidak memblokir saat menunggu data.
4. Mengimplementasikan Iterator Asinkron Kustom
Fungsi generator asinkron memudahkan pembuatan iterator asinkron kustom untuk sumber data asinkron apa pun. Anda dapat menentukan logika Anda sendiri untuk mengambil, memproses, dan menghasilkan nilai.
Contoh (Menghasilkan Urutan Angka Secara Asinkron):
async function* generateNumbers(start, end, delay) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, delay));
yield i;
}
}
async function main() {
for await (const number of generateNumbers(1, 5, 500)) {
console.log(number);
}
}
main();
Contoh ini menghasilkan urutan angka dari start
hingga end
, dengan delay
yang ditentukan antara setiap angka. Baris await new Promise(resolve => setTimeout(resolve, delay))
memperkenalkan penundaan asinkron.
Penanganan Kesalahan
Penanganan kesalahan sangat penting saat bekerja dengan fungsi generator asinkron. Anda dapat menggunakan blok try...catch
di dalam fungsi generator untuk menangani kesalahan yang terjadi selama operasi asinkron.
Contoh (Penanganan Kesalahan dalam Generator Asinkron):
async function* fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
yield data;
} catch (error) {
console.error('Error fetching data:', error);
// Anda dapat memilih untuk melemparkan kembali kesalahan, menghasilkan nilai default, atau menghentikan iterasi.
// Misalnya, yield { error: error.message };
throw error;
}
}
async function main() {
try {
for await (const data of fetchData('https://example.com/api/invalid')) {
console.log(data);
}
} catch (error) {
console.error('Error during iteration:', error);
}
}
main();
Contoh ini menunjukkan cara menangani kesalahan yang mungkin terjadi selama operasi fetch
. Blok try...catch
menangkap kesalahan apa pun dan mencatatnya ke konsol. Anda juga dapat melemparkan kembali kesalahan agar ditangkap oleh konsumen generator, atau menghasilkan objek kesalahan.
Manfaat Menggunakan Fungsi Generator Asinkron
- Peningkatan Keterbacaan Kode: Fungsi generator asinkron membuat kode iterasi asinkron lebih mudah dibaca dan dipelihara dibandingkan dengan pendekatan berbasis Promise tradisional.
- Alur Kontrol Asinkron yang Disederhanakan: Mereka menyediakan cara yang lebih alami dan berurutan untuk mengekspresikan logika asinkron, membuatnya lebih mudah untuk dipahami.
- Pengelolaan Sumber Daya yang Efisien: Mereka memungkinkan Anda memproses data dalam potongan atau aliran, mengurangi konsumsi memori dan meningkatkan kinerja, terutama saat berurusan dengan dataset besar atau aliran data real-time.
- Pemisahan Masalah yang Jelas: Mereka memisahkan logika untuk menghasilkan data dari logika untuk mengonsumsi data, mempromosikan modularitas dan penggunaan kembali.
Perbandingan dengan Pendekatan Asinkron Lainnya
Generator Asinkron vs. Promises
Meskipun Promises sangat penting untuk operasi asinkron, mereka kurang cocok untuk menangani urutan nilai asinkron. Generator asinkron menyediakan cara yang lebih terstruktur dan efisien untuk melakukan iterasi melalui aliran data asinkron.
Generator Asinkron vs. RxJS Observables
RxJS Observables adalah alat ampuh lainnya untuk menangani aliran data asinkron. Observables menawarkan fitur yang lebih canggih seperti operator untuk mengubah, memfilter, dan menggabungkan aliran data. Namun, generator asinkron seringkali lebih mudah digunakan untuk skenario iterasi asinkron dasar.
Kompatibilitas Browser dan Node.js
Fungsi generator asinkron didukung secara luas di browser modern dan Node.js. Mereka tersedia di semua browser utama yang mendukung ES2018 (ECMAScript 2018) dan Node.js versi 10 ke atas.
Anda dapat menggunakan alat seperti Babel untuk mentranspil kode Anda ke versi JavaScript yang lebih lama jika Anda perlu mendukung lingkungan yang lebih lama.
Kesimpulan
Fungsi generator asinkron merupakan tambahan yang berharga untuk toolkit pemrograman asinkron JavaScript. Mereka menyediakan cara yang ampuh dan elegan untuk menangani iterasi asinkron, membuatnya lebih mudah untuk memproses aliran data secara efisien dan terpelihara. Dengan memahami protokol iterasi asinkron dan sintaks fungsi generator asinkron, Anda dapat memanfaatkan manfaatnya dalam berbagai aplikasi, mulai dari streaming data dari API hingga memproses file besar dan menangani aliran data real-time.
Pembelajaran Lebih Lanjut
- MDN Web Docs: AsyncGeneratorFunction
- Exploring ES2018: Asynchronous Iteration
- Node.js Documentation: Konsultasikan dokumentasi Node.js resmi untuk aliran dan operasi sistem file.