Avastage JavaScript'i asünkroonsed generaatorid tõhusaks voogude töötlemiseks. Õppige, kuidas luua, tarbida ja rakendada asünkroonseid generaatoreid skaleeritavate ja reageerimisvõimeliste rakenduste ehitamiseks.
JavaScript'i asünkroonsed generaatorid: Voogude töötlemine kaasaegsetes rakendustes
Pidevalt arenevas JavaScripti arendusmaailmas on asünkroonsete andmevoogude tõhus käsitlemine ülioluline. Traditsioonilised lähenemisviisid võivad suurte andmekogumite või reaalajas voogudega tegelemisel muutuda kohmakaks. Siin tulevadki mängu asünkroonsed generaatorid, pakkudes võimsa ja elegantse lahenduse voogude töötlemiseks.
Mis on asünkroonsed generaatorid?
Asünkroonsed generaatorid on spetsiaalne JavaScripti funktsiooni tüüp, mis võimaldab teil genereerida väärtusi asünkroonselt, ükshaaval. Need on kombinatsioon kahest võimsast kontseptsioonist: asünkroonne programmeerimine ja generaatorid.
- Asünkroonne programmeerimine: Võimaldab mitteblokeerivaid operatsioone, mis lubavad teie koodil jätkata täitmist, oodates samal ajal pikaajaliste ülesannete (nagu võrgupäringud või failide lugemine) lõpuleviimist.
- Generaatorid: Funktsioonid, mida saab peatada ja jätkata, andes väärtusi iteratiivselt.
Mõelge asünkroonsest generaatorist kui funktsioonist, mis suudab toota väärtuste jada asünkroonselt, peatades täitmise pärast iga väärtuse andmist ja jätkates siis, kui järgmist väärtust küsitakse.
Asünkroonsete generaatorite põhiomadused:
- Asünkroonne väärtuste andmine: Kasutage
yield
võtmesõna väärtuste tootmiseks jaawait
võtmesõna asünkroonsete operatsioonide käsitlemiseks generaatori sees. - Itereeritavus: Asünkroonsed generaatorid tagastavad asünkroonse iteraatori, mida saab tarbida kasutades
for await...of
tsükleid. - Laisk hindamine: Väärtused genereeritakse ainult siis, kui neid küsitakse, mis parandab jõudlust ja mälukasutust, eriti suurte andmekogumitega tegelemisel.
- Vigade käsitlemine: Saate vigu käsitleda generaatori funktsiooni sees, kasutades
try...catch
plokke.
Asünkroonsete generaatorite loomine
Asünkroonse generaatori loomiseks kasutage süntaksit async function*
:
async function* myAsyncGenerator() {
yield await Promise.resolve(1);
yield await Promise.resolve(2);
yield await Promise.resolve(3);
}
Vaatame seda näidet lähemalt:
async function* myAsyncGenerator()
: Deklareerib asünkroonse generaatori funktsiooni nimegamyAsyncGenerator
.yield await Promise.resolve(1)
: Annab asünkroonselt väärtuse1
. Võtmesõnaawait
tagab, et lubadus lahendatakse enne väärtuse andmist.
Asünkroonsete generaatorite tarbimine
Saate asünkroonseid generaatoreid tarbida, kasutades for await...of
tsüklit:
async function consumeGenerator() {
for await (const value of myAsyncGenerator()) {
console.log(value);
}
}
consumeGenerator(); // Väljund: 1, 2, 3 (trükitakse asünkroonselt)
Tsükkel for await...of
itereerib üle asünkroonse generaatori antud väärtuste, oodates iga väärtuse asünkroonset lahendamist enne järgmise iteratsiooniga jätkamist.
Asünkroonsete generaatorite praktilised näited voogude töötlemisel
Asünkroonsed generaatorid sobivad eriti hästi stsenaariumideks, mis hõlmavad voogude töötlemist. Uurime mõningaid praktilisi näiteid:
1. Suurte failide asünkroonne lugemine
Suurte failide mällu lugemine võib olla ebaefektiivne ja mälumahukas. Asünkroonsed generaatorid võimaldavad teil töödelda faile tükkidena, vähendades mälujalajälge ja parandades jõudlust.
const fs = require('fs');
const readline = require('readline');
async function* readFileByLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
async function processFile(filePath) {
for await (const line of readFileByLines(filePath)) {
// Töötle iga faili rida
console.log(line);
}
}
processFile('path/to/your/largefile.txt');
Selles näites:
readFileByLines
on asünkroonne generaator, mis loeb faili rida-realt, kasutadesreadline
moodulit.fs.createReadStream
loob failist loetava voo.readline.createInterface
loob liidese voo rida-realt lugemiseks.- Tsükkel
for await...of
itereerib üle faili ridade, andes iga rea asünkroonselt. processFile
tarbib asünkroonset generaatorit ja töötleb iga rida.
See lähenemine on eriti kasulik logifailide, andmete väljavõtete või mis tahes suurte tekstipõhiste andmekogumite töötlemiseks.
2. Andmete pärimine API-dest lehekülgede kaupa (paginatsioon)
Paljud API-d kasutavad paginatsiooni, tagastades andmeid tükkidena. Asünkroonsed generaatorid võivad lihtsustada andmete pärimise ja töötlemise protsessi mitme lehekülje lõikes.
async function* fetchPaginatedData(url, pageSize) {
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(`${url}?page=${page}&pageSize=${pageSize}`);
const data = await response.json();
if (data.items.length === 0) {
hasMore = false;
break;
}
for (const item of data.items) {
yield item;
}
page++;
}
}
async function processData() {
for await (const item of fetchPaginatedData('https://api.example.com/data', 20)) {
// Töötle iga elementi
console.log(item);
}
}
processData();
Selles näites:
fetchPaginatedData
on asünkroonne generaator, mis pärib andmeid API-st, käsitledes paginatsiooni automaatselt.- See pärib andmeid igalt leheküljelt, andes iga elemendi eraldi.
- Tsükkel jätkub, kuni API tagastab tühja lehekülje, mis näitab, et rohkem elemente pole.
processData
tarbib asünkroonset generaatorit ja töötleb iga elementi.
See muster on levinud suhtlemisel API-dega nagu Twitteri API, GitHubi API või mis tahes API-ga, mis kasutab suurte andmekogumite haldamiseks paginatsiooni.
3. Reaalajas andmevoogude töötlemine (nt WebSockets)
Asünkroonseid generaatoreid saab kasutada reaalajas andmevoogude töötlemiseks allikatest nagu WebSockets või Server-Sent Events (SSE).
async function* processWebSocketStream(url) {
const ws = new WebSocket(url);
ws.onmessage = (event) => {
// Tavaliselt lükkaksite siin andmed järjekorda
// ja seejärel annaksite `yield` järjekorrast, et vältida
// onmessage käsitleja blokeerimist. Lihtsuse huvides anname otse.
yield JSON.parse(event.data);
};
ws.onerror = (error) => {
console.error('WebSocketi viga:', error);
};
ws.onclose = () => {
console.log('WebSocketi ühendus suletud.');
};
// Hoia generaator elus, kuni ühendus suletakse.
// See on lihtsustatud lähenemine; kaaluge järjekorra
// ja mehhanismi kasutamist, et generaatorile lõpetamise signaal anda.
await new Promise(resolve => ws.onclose = resolve);
}
async function consumeWebSocketData() {
for await (const data of processWebSocketStream('wss://example.com/websocket')) {
// Töötle reaalajas andmeid
console.log(data);
}
}
consumeWebSocketData();
Olulised kaalutlused WebSocketi voogude puhul:
- Vasturõhk (Backpressure): Reaalajas vood võivad toota andmeid kiiremini, kui tarbija suudab neid töödelda. Rakendage vasturõhu mehhanisme, et vältida tarbija ülekoormamist. Üks levinud lähenemine on kasutada järjekorda sissetulevate andmete puhverdamiseks ja anda WebSocketile signaal andmete saatmise peatamiseks, kui järjekord on täis.
- Vigade käsitlemine: Käsitsege WebSocketi vigu sujuvalt, sealhulgas ühenduse vigu ja andmete parsimise vigu.
- Ühenduse haldamine: Rakendage uuestiühendamise loogikat, et automaatselt WebSocketiga uuesti ühendust luua, kui ühendus kaob.
- Puhverdamine: Eespool mainitud järjekorra kasutamine võimaldab teil eraldada andmete saabumise kiiruse websocketis nende töötlemise kiirusest. See kaitseb lühiajaliste andmemahu hüpete eest, mis võivad põhjustada vigu.
See näide illustreerib lihtsustatud stsenaariumi. Tugevam implementatsioon hõlmaks järjekorda sissetulevate sõnumite haldamiseks ja vasturõhu tõhusaks käsitlemiseks.
4. Puustruktuuride asünkroonne läbimine
Asünkroonsed generaatorid on kasulikud ka keeruliste puustruktuuride läbimiseks, eriti kui iga sõlm võib nõuda asünkroonset operatsiooni (nt andmete pärimine andmebaasist).
async function* traverseTree(node) {
yield node;
if (node.children) {
for (const child of node.children) {
yield* traverseTree(child); // Kasutage yield*, et delegeerida teisele generaatorile
}
}
}
// Näidis puustruktuur
const tree = {
value: 'A',
children: [
{ value: 'B', children: [{value: 'D'}] },
{ value: 'C' }
]
};
async function processTree() {
for await (const node of traverseTree(tree)) {
console.log(node.value); // Väljund: A, B, D, C
}
}
processTree();
Selles näites:
traverseTree
on asünkroonne generaator, mis läbib rekursiivselt puustruktuuri.- See annab iga sõlme puus.
- Võtmesõna
yield*
delegeerib teisele generaatorile, võimaldades teil rekursiivsete kutsete tulemusi tasandada. processTree
tarbib asünkroonset generaatorit ja töötleb iga sõlme.
Vigade käsitlemine asünkroonsete generaatoritega
Saate kasutada try...catch
plokke asünkroonsetes generaatorites, et käsitleda vigu, mis võivad tekkida asünkroonsete operatsioonide käigus.
async function* myAsyncGeneratorWithErrors() {
try {
const result = await someAsyncFunction();
yield result;
} catch (error) {
console.error('Viga generaatoris:', error);
// Saate valida, kas visata viga uuesti või anda spetsiaalne vea väärtus
yield { error: error.message }; // Veaobjekti andmine
}
yield await Promise.resolve('Jätkamine pärast viga (kui ei visatud uuesti)');
}
async function consumeGeneratorWithErrors() {
for await (const value of myAsyncGeneratorWithErrors()) {
if (value.error) {
console.error('Saadi generaatorist viga:', value.error);
} else {
console.log(value);
}
}
}
consumeGeneratorWithErrors();
Selles näites:
try...catch
plokk püüab kinni kõik vead, mis võivad tekkidaawait someAsyncFunction()
kutse ajal.catch
plokk logib vea ja annab veaobjekti.- Tarbija saab kontrollida
error
omadust ja käsitleda viga vastavalt.
Asünkroonsete generaatorite kasutamise eelised voogude töötlemisel
- Parem jõudlus: Laisk hindamine ja asünkroonne töötlemine võivad oluliselt parandada jõudlust, eriti suurte andmekogumite või reaalajas voogudega tegelemisel.
- Vähendatud mälukasutus: Andmete töötlemine tükkidena vähendab mälujalajälge, võimaldades teil käsitleda andmekogumeid, mis muidu oleksid liiga suured mällu mahtumiseks.
- Parem koodi loetavus: Asünkroonsed generaatorid pakuvad asünkroonsete andmevoogude käsitlemiseks lühemat ja loetavamat viisi võrreldes traditsiooniliste tagasikutsepõhiste lähenemistega.
- Parem vigade käsitlemine:
try...catch
plokid generaatorite sees lihtsustavad vigade käsitlemist. - Lihtsustatud asünkroonne kontrollivoog:
async/await
kasutamine generaatori sees muudab selle lugemise ja jälgimise palju lihtsamaks kui teised asünkroonsed konstruktsioonid.
Millal kasutada asünkroonseid generaatoreid
Kaaluge asünkroonsete generaatorite kasutamist järgmistes stsenaariumides:
- Suurte failide või andmekogumite töötlemine.
- Andmete pärimine API-dest lehekülgede kaupa.
- Reaalajas andmevoogude käsitlemine (nt WebSockets, SSE).
- Keeruliste puustruktuuride läbimine.
- Igas olukorras, kus peate töötlema andmeid asünkroonselt ja iteratiivselt.
Asünkroonsed generaatorid vs. Observables
Nii asünkroonseid generaatoreid kui ka Observable'eid kasutatakse asünkroonsete andmevoogude käsitlemiseks, kuid neil on erinevad omadused:
- Asünkroonsed generaatorid: Pull-põhised (tõmbepõhised), mis tähendab, et tarbija küsib andmeid generaatorilt.
- Observables: Push-põhised (tõukepõhised), mis tähendab, et tootja lükkab andmeid tarbijale.
Valige asünkroonsed generaatorid, kui soovite peeneteralist kontrolli andmevoo üle ja peate töötlema andmeid kindlas järjekorras. Valige Observables, kui peate käsitlema reaalajas vooge mitme tellija ja keerukate teisendustega.
Kokkuvõte
JavaScript'i asünkroonsed generaatorid pakuvad võimsa ja elegantse lahenduse voogude töötlemiseks. Kombineerides asünkroonse programmeerimise ja generaatorite eeliseid, võimaldavad need teil ehitada skaleeritavaid, reageerimisvõimelisi ja hooldatavaid rakendusi, mis suudavad tõhusalt käsitleda suuri andmekogumeid ja reaalajas vooge. Võtke asünkroonsed generaatorid omaks, et avada uusi võimalusi oma JavaScripti arendustöös.