Syväsukellus async-generaattorifunktioihin JavaScriptissä, tutustuen asynkronisiin iteraatioprotokolliin, käyttötapauksiin ja käytännön esimerkkeihin.
Async-generaattorifunktiot: Asynkronisen iteraatioprotokollien hallinta
Asynkroninen ohjelmointi on modernin JavaScript-kehityksen kulmakivi, erityisesti käsiteltäessä I/O-toimintoja, kuten datan hakemista API-rajapinnoista, tiedostojen lukemista tai tietokantojen kanssa vuorovaikuttamista. Perinteisesti olemme luottaneet Promiseihin ja async/awaitiin näiden asynkronisten tehtävien hallinnassa. Async-generaattorifunktiot tarjoavat kuitenkin tehokkaan ja elegantin tavan käsitellä asynkronista iteraatiota, mahdollistaen datavirtojen käsittelyn asynkronisesti ja tehokkaasti.
Asynkronisten iteraatioprotokollien ymmärtäminen
Ennen async-generaattorifunktioihin syventymistä on olennaista ymmärtää asynkroniset iteraatioprotokollat, joille ne perustuvat. Nämä protokollat määrittelevät, miten asynkronisia datalähteitä voidaan iteroida hallitusti ja ennakoitavasti.
Asynkroninen iteroitava-protokolla
Asynkroninen iteroitava-protokolla määrittelee objektin, jota voidaan iteroida asynkronisesti. Objekti noudattaa tätä protokollaa, jos sillä on metodi, jonka avain on Symbol.asyncIterator
ja joka palauttaa asynkronisen iteraattorin.
Ajattele iteroitavaa kuin kappalelistaa. Asynkroninen iteroitava on kuin soittolista, jossa jokainen kappale on ladattava (asynkronisesti) ennen kuin sitä voidaan soittaa.
Esimerkki:
const asyncIterable = {
[Symbol.asyncIterator]() {
return {
next() {
// Hae seuraava arvo asynkronisesti
}
};
}
};
Asynkroninen iteraattori-protokolla
Asynkroninen iteraattori-protokolla määrittelee metodit, jotka asynkronisen iteraattorin on toteutettava. Protokollaa noudattavan objektin on sisällettävä next()
-metodi ja valinnaisesti return()
- ja throw()
-metodit.
- next(): Tämä metodi palauttaa Promisen, joka ratkeaa objektiin, jolla on kaksi ominaisuutta:
value
jadone
.value
sisältää sekvenssin seuraavan arvon, jadone
on totuusarvo, joka osoittaa, onko iteraatio suoritettu. - return(): (Valinnainen) Tämä metodi palauttaa Promisen, joka ratkeaa
value
- jadone
-ominaisuuksilla varustettuun objektiin. Se ilmaisee, että iteraattori suljetaan. Tämä on hyödyllistä resurssien vapauttamisessa. - throw(): (Valinnainen) Tämä metodi palauttaa Promisen, joka hylätään virheellä. Sitä käytetään ilmoittamaan virheen tapahtumisesta iteraation aikana.
Esimerkki:
const asyncIterator = {
next() {
return new Promise((resolve) => {
// Hae seuraava arvo asynkronisesti
setTimeout(() => {
resolve({ value: /* jokin arvo */, done: false });
}, 100);
});
},
return() {
return Promise.resolve({ value: undefined, done: true });
},
throw(error) {
return Promise.reject(error);
}
};
Async-generaattorifunktioiden esittely
Async-generaattorifunktiot tarjoavat kätevämmän ja luettavamman tavan luoda asynkronisia iteraattoreita ja iteroitavia. Ne yhdistävät generaattorien tehon Promise-objektien asynkronisuuteen.
Syntaksi
Async-generaattorifunktio määritellään käyttämällä async function*
-syntaksia:
async function* myAsyncGenerator() {
// Asynkroniset operaatiot ja yield-lauseet tässä
}
yield
-avainsana
Async-generaattorifunktion sisällä yield
-avainsanaa käytetään arvojen tuottamiseen asynkronisesti. Jokainen yield
-lause pysäyttää generaattorifunktion suorituksen, kunnes luovutettu Promise ratkeaa.
Esimerkki:
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;
}
Async-generaattorien kuluttaminen for await...of
-rakenteella
Voit iteroida async-generaattorifunktion tuottamien arvojen läpi käyttämällä for await...of
-silmukkaa. Tämä silmukka käsittelee automaattisesti generaattorin tuottamien Promise-objektien asynkronisen ratkaisun.
Esimerkki:
async function main() {
for await (const user of fetchUsers()) {
console.log(user);
}
}
main();
Käytännön käyttötapaukset async-generaattorifunktioille
Async-generaattorifunktiot loistavat skenaarioissa, jotka liittyvät asynkronisiin datavirtoihin, kuten:
1. Datan striimaus API-rajapinnoista
Kuvittele suuren tietojoukon hakemista API-rajapinnasta, joka tukee sivutusta. Sen sijaan, että hakisit koko tietojoukon kerralla, voit käyttää async-generaattorifunktiota sivujen hakemiseen ja tuottamiseen asteittain.
Esimerkki (Sivutetun datan hakeminen):
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; // Ei enempää dataa
}
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();
Kansainvälinen esimerkki (Valuutanvaihtokurssien API):
async function* fetchExchangeRates(currencyPair, startDate, endDate) {
let currentDate = new Date(startDate);
while (currentDate <= new Date(endDate)) {
const dateString = currentDate.toISOString().split('T')[0]; // VVVV-PP-PP
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(`Virhe datan haussa päivämäärälle ${dateString}:`, error);
// Voit käsitellä virheitä eri tavoin, esimerkiksi yrittää uudelleen tai ohittaa päivämäärän.
}
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();
Tämä esimerkki hakee päivittäiset EUR-USD-vaihtokurssit annetulle aikavälille. Se käsittelee mahdolliset API-kutsujen virheet. Muista korvata `https://api.exchangerate.host` luotettavalla ja asianmukaisella API-päätepisteellä.
2. Suurten tiedostojen käsittely
Suurten tiedostojen kanssa työskennellessä koko tiedoston lataaminen muistiin voi olla tehotonta. Async-generaattorifunktiot mahdollistavat tiedoston lukemisen rivi kerrallaan tai paloina, ja kunkin palan käsittelyn asynkronisesti.
Esimerkki (Suuren tiedoston lukeminen rivi kerrallaan - 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')) {
// Käsittele jokaista riviä asynkronisesti
console.log(line);
}
}
main();
Tämä Node.js-esimerkki näyttää tiedoston lukemisen rivi kerrallaan käyttämällä fs.createReadStream
ja readline.createInterface
-ominaisuuksia. readLines
-niminen async-generaattorifunktio tuottaa jokaisen rivin asynkronisesti.
3. Reaaliaikaisten datavirtojen käsittely (WebSockets, Server-Sent Events)
Async-generaattorifunktiot soveltuvat hyvin reaaliaikaisten datavirtojen käsittelyyn lähteistä, kuten WebSockets tai Server-Sent Events (SSE). Voit jatkuvasti tuottaa dataa sen saapuessa virrasta.
Esimerkki (Datan käsittely WebSocketista - käsitteellinen):
// Tämä on käsitteellinen esimerkki ja vaatii WebSocket-kirjaston, kuten 'ws' (Node.js) tai selaimen sisäänrakennetun WebSocket API:n.
async function* processWebSocketStream(url) {
const websocket = new WebSocket(url);
websocket.onmessage = (event) => {
//Tämä on käsiteltävä generaattorin ulkopuolella.
//Tyypillisesti event.data työnnettäisiin jonoon
//ja generaattori vetäisi jonosta asynkronisesti
//Promisen avulla, joka ratkeaa, kun dataa on saatavilla.
};
websocket.onerror = (error) => {
//Käsittele virheet.
};
websocket.onclose = () => {
//Käsittele sulkeminen.
}
//Varsinainen tuottaminen ja jonon hallinta tapahtuisi tässä,
//käyttäen Promise-objekteja synkronointiin websocket.onmessage
//tapahtuman ja async-generaattorifunktion välillä.
//Tämä on yksinkertaistettu kuvaus.
//while(true){ //Käytä tätä, jos jonotat tapahtumia oikein.
// const data = await new Promise((resolve) => {
// // Ratkaise promise, kun dataa on saatavilla jonosta.
// })
// yield data
//}
}
async function main() {
// for await (const message of processWebSocketStream('wss://example.com/ws')) {
// console.log(message);
// }
console.log("WebSocket-esimerkki - vain käsitteellinen. Katso kommentit koodissa yksityiskohdista.");
}
main();
Tärkeitä huomioita WebSocket-esimerkistä:
- Annettu WebSocket-esimerkki on pääasiassa käsitteellinen, koska WebSocketin tapahtumapohjaisen luonteen ja async-generaattorien suora integrointi vaatii huolellista synkronointia Promise-objektien ja jonojen avulla.
- Todelliset toteutukset sisältävät tyypillisesti saapuvien WebSocket-viestien puskuroinnin jonoon ja Promisen käytön async-generaattorille ilmoittamaan, kun uutta dataa on saatavilla. Tämä varmistaa, ettei generaattori jää odottamaan dataa blokkaavasti.
4. Mukautettujen asynkronisten iteraattorien toteuttaminen
Async-generaattorifunktiot helpottavat mukautettujen asynkronisten iteraattorien luomista mille tahansa asynkroniselle datalähteelle. Voit määritellä oman logiikkasi arvojen hakemiseen, käsittelyyn ja tuottamiseen.
Esimerkki (Asynkronisesti numerosekvenssin tuottaminen):
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();
Tämä esimerkki tuottaa numerosekvenssin start
-arvosta end
-arvoon, tietyllä delay
-viiveellä jokaisen numeron välillä. await new Promise(resolve => setTimeout(resolve, delay))
-rivi lisää asynkronisen viiveen.
Virheidenkäsittely
Virheidenkäsittely on olennaista async-generaattorifunktioiden kanssa työskennellessä. Voit käyttää try...catch
-lohkoja generaattorifunktion sisällä käsitelläksesi asynkronisten operaatioiden aikana tapahtuvia virheitä.
Esimerkki (Virheidenkäsittely Async-generaattorissa):
async function* fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
yield data;
} catch (error) {
console.error('Virhe datan haussa:', error);
// Voit valita virheen uudelleenheittämisen, oletusarvon tuottamisen tai iteraation lopettamisen.
// Esimerkiksi, tuota { 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('Virhe iteraation aikana:', error);
}
}
main();
Tämä esimerkki havainnollistaa, kuinka fetch
-operaation aikana mahdollisesti tapahtuvia virheitä käsitellään. try...catch
-lohko nappaa kaikki virheet ja kirjaa ne konsoliin. Voit myös heittää virheen uudelleen, jotta generaattorin kuluttaja voi napata sen, tai tuottaa virheobjektin.
Async-generaattorifunktioiden käytön edut
- Parannettu koodin luettavuus: Async-generaattorifunktiot tekevät asynkronisesta iteraatiokoodista luettavampaa ja ylläpidettävämpää verrattuna perinteisiin Promise-pohjaisiin lähestymistapoihin.
- Yksinkertaistettu asynkroninen ohjausvirta: Ne tarjoavat luonnollisemman ja peräkkäisemmän tavan ilmaista asynkronista logiikkaa, mikä helpottaa sen ymmärtämistä.
- Tehokas resurssienhallinta: Ne mahdollistavat datan käsittelyn paloina tai virtoina, vähentäen muistin kulutusta ja parantaen suorituskykyä, erityisesti käsiteltäessä suuria tietojoukkoja tai reaaliaikaisia datavirtoja.
- Selkeä vastuunjako: Ne erottavat datan tuottamislogiikan datan kulutuslogiikasta, edistäen modulaarisuutta ja uudelleenkäytettävyyttä.
Vertailu muihin asynkronisiin lähestymistapoihin
Async-generaattorit vs. Promises
Vaikka Promiset ovat perustavanlaatuisia asynkronisille operaatioille, ne eivät sovellu hyvin asynkronisten arvojen sarjojen käsittelyyn. Async-generaattorit tarjoavat jäsennellymmän ja tehokkaamman tavan iteroida asynkronisia datavirtoja.
Async-generaattorit vs. RxJS Observables
RxJS Observables ovat toinen tehokas työkalu asynkronisten datavirtojen käsittelyyn. Observables tarjoavat edistyneempiä ominaisuuksia, kuten operaattoreita datavirtojen muuntamiseen, suodattamiseen ja yhdistämiseen. Async-generaattorit ovat kuitenkin usein yksinkertaisempia käyttää perus asynkronisen iteraation skenaarioissa.
Selaimen ja Node.js-yhteensopivuus
Async-generaattorifunktiot ovat laajalti tuettuja moderneissa selaimissa ja Node.js:ssä. Ne ovat saatavilla kaikissa suurimmissa selaimissa, jotka tukevat ES2018:aa (ECMAScript 2018), ja Node.js-versioissa 10 ja uudemmissa.
Voit käyttää työkaluja, kuten Babel, koodin kääntämiseen vanhempiin JavaScript-versioihin, jos sinun on tuettava vanhempia ympäristöjä.
Yhteenveto
Async-generaattorifunktiot ovat arvokas lisäys JavaScriptin asynkroniseen ohjelmointityökalupakkiin. Ne tarjoavat tehokkaan ja elegantin tavan käsitellä asynkronista iteraatiota, mikä helpottaa datavirtojen käsittelyä tehokkaasti ja ylläpidettävästi. Ymmärtämällä asynkroniset iteraatioprotokollat ja async-generaattorifunktioiden syntaksin, voit hyödyntää niiden etuja monenlaisissa sovelluksissa, aina datan striimaamisesta API-rajapinnoista suurten tiedostojen käsittelyyn ja reaaliaikaisten datavirtojen hallintaan.
Lisää opiskeltavaa
- MDN Web Docs: AsyncGeneratorFunction
- Exploring ES2018: Asynkroninen iteraatio
- Node.js-dokumentaatio: Katso virallista Node.js-dokumentaatiota striimeihin ja tiedostojärjestelmäoperaatioihin.