Odkryj wzorzec asynchronicznego iteratora w JavaScript do wydajnego przetwarzania strumieni danych. Naucz si臋 implementowa膰 iteracj臋 asynchroniczn膮 do obs艂ugi du偶ych zbior贸w danych, odpowiedzi API i strumieni w czasie rzeczywistym, z praktycznymi przyk艂adami i przypadkami u偶ycia.
Wzorzec asynchronicznego iteratora w JavaScript: Kompleksowy przewodnik po projektowaniu strumieni
We wsp贸艂czesnym tworzeniu aplikacji w JavaScript, zw艂aszcza w przypadku aplikacji intensywnie przetwarzaj膮cych dane lub strumieni danych w czasie rzeczywistym, potrzeba wydajnego i asynchronicznego przetwarzania danych jest najwa偶niejsza. Wzorzec iteratora asynchronicznego, wprowadzony wraz z ECMAScript 2018, dostarcza pot臋偶ne i eleganckie rozwi膮zanie do asynchronicznej obs艂ugi strumieni danych. Ten wpis na blogu zag艂臋bia si臋 w tajniki wzorca iteratora asynchronicznego, badaj膮c jego koncepcje, implementacj臋, przypadki u偶ycia i zalety w r贸偶nych scenariuszach. To rewolucyjne rozwi膮zanie do wydajnej i asynchronicznej obs艂ugi strumieni danych, kluczowe dla nowoczesnych aplikacji internetowych na ca艂ym 艣wiecie.
Zrozumienie iterator贸w i generator贸w
Zanim zag艂臋bimy si臋 w iteratory asynchroniczne, przypomnijmy sobie kr贸tko podstawowe koncepcje iterator贸w i generator贸w w JavaScript. Stanowi膮 one fundament, na kt贸rym zbudowane s膮 iteratory asynchroniczne.
Iteratory
Iterator to obiekt, kt贸ry definiuje sekwencj臋 i, po zako艅czeniu, potencjalnie warto艣膰 zwrotn膮. W szczeg贸lno艣ci, iterator implementuje metod臋 next(), kt贸ra zwraca obiekt z dwiema w艂a艣ciwo艣ciami:
value: nast臋pna warto艣膰 w sekwencji.done: warto艣膰 logiczna (boolean) wskazuj膮ca, czy iterator zako艅czy艂 iteracj臋 przez sekwencj臋. Gdydonejesttrue,valuejest zazwyczaj warto艣ci膮 zwrotn膮 iteratora, je艣li taka istnieje.
Oto prosty przyk艂ad synchronicznego iteratora:
const myIterator = {
data: [1, 2, 3],
index: 0,
next() {
if (this.index < this.data.length) {
return { value: this.data[this.index++], done: false };
} else {
return { value: undefined, done: true };
}
},
};
console.log(myIterator.next()); // Output: { value: 1, done: false }
console.log(myIterator.next()); // Output: { value: 2, done: false }
console.log(myIterator.next()); // Output: { value: 3, done: false }
console.log(myIterator.next()); // Output: { value: undefined, done: true }
Generatory
Generatory zapewniaj膮 bardziej zwi臋z艂y spos贸b definiowania iterator贸w. S膮 to funkcje, kt贸re mo偶na wstrzymywa膰 i wznawia膰, co pozwala na bardziej naturalne zdefiniowanie algorytmu iteracyjnego za pomoc膮 s艂owa kluczowego yield.
Oto ten sam przyk艂ad, co powy偶ej, ale zaimplementowany przy u偶yciu funkcji generatora:
function* myGenerator(data) {
for (let i = 0; i < data.length; i++) {
yield data[i];
}
}
const iterator = myGenerator([1, 2, 3]);
console.log(iterator.next()); // Output: { value: 1, done: false }
console.log(iterator.next()); // Output: { value: 2, done: false }
console.log(iterator.next()); // Output: { value: 3, done: false }
console.log(iterator.next()); // Output: { value: undefined, done: true }
S艂owo kluczowe yield wstrzymuje funkcj臋 generatora i zwraca okre艣lon膮 warto艣膰. Generator mo偶na p贸藕niej wznowi膰 od miejsca, w kt贸rym zosta艂 przerwany.
Wprowadzenie do iterator贸w asynchronicznych
Iteratory asynchroniczne rozszerzaj膮 koncepcj臋 iterator贸w do obs艂ugi operacji asynchronicznych. S膮 zaprojektowane do pracy ze strumieniami danych, w kt贸rych ka偶dy element jest pobierany lub przetwarzany asynchronicznie, na przyk艂ad podczas pobierania danych z API lub odczytywania z pliku. Jest to szczeg贸lnie przydatne w 艣rodowiskach Node.js lub podczas pracy z danymi asynchronicznymi w przegl膮darce. Zwi臋ksza to responsywno艣膰, zapewniaj膮c lepsze do艣wiadczenia u偶ytkownika i ma znaczenie globalne.
Iterator asynchroniczny implementuje metod臋 next(), kt贸ra zwraca obietnic臋 (Promise), kt贸ra z kolei rozwi膮zuje si臋 do obiektu z w艂a艣ciwo艣ciami value i done, podobnie jak w przypadku iterator贸w synchronicznych. Kluczow膮 r贸偶nic膮 jest to, 偶e metoda next() zwraca teraz obietnic臋, co pozwala na operacje asynchroniczne.
Definiowanie iteratora asynchronicznego
Oto przyk艂ad podstawowego iteratora asynchronicznego:
const myAsyncIterator = {
data: [1, 2, 3],
index: 0,
async next() {
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate async operation
if (this.index < this.data.length) {
return { value: this.data[this.index++], done: false };
} else {
return { value: undefined, done: true };
}
},
};
async function consumeIterator() {
console.log(await myAsyncIterator.next()); // Output: { value: 1, done: false }
console.log(await myAsyncIterator.next()); // Output: { value: 2, done: false }
console.log(await myAsyncIterator.next()); // Output: { value: 3, done: false }
console.log(await myAsyncIterator.next()); // Output: { value: undefined, done: true }
}
consumeIterator();
W tym przyk艂adzie metoda next() symuluje operacj臋 asynchroniczn膮 za pomoc膮 setTimeout. Funkcja consumeIterator nast臋pnie u偶ywa await, aby poczeka膰 na rozwi膮zanie obietnicy zwr贸conej przez next() przed zalogowaniem wyniku.
Generatory asynchroniczne
Podobnie jak generatory synchroniczne, generatory asynchroniczne zapewniaj膮 wygodniejszy spos贸b tworzenia iterator贸w asynchronicznych. S膮 to funkcje, kt贸re mo偶na wstrzymywa膰 i wznawia膰, i u偶ywaj膮 s艂owa kluczowego yield do zwracania obietnic (Promises).
Aby zdefiniowa膰 generator asynchroniczny, u偶yj sk艂adni async function*. Wewn膮trz generatora mo偶na u偶ywa膰 s艂owa kluczowego await do wykonywania operacji asynchronicznych.
Oto ten sam przyk艂ad, co powy偶ej, zaimplementowany przy u偶yciu generatora asynchronicznego:
async function* myAsyncGenerator(data) {
for (let i = 0; i < data.length; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate async operation
yield data[i];
}
}
async function consumeGenerator() {
const iterator = myAsyncGenerator([1, 2, 3]);
console.log(await iterator.next()); // Output: { value: 1, done: false }
console.log(await iterator.next()); // Output: { value: 2, done: false }
console.log(await iterator.next()); // Output: { value: 3, done: false }
console.log(await iterator.next()); // Output: { value: undefined, done: true }
}
consumeGenerator();
Konsumowanie iterator贸w asynchronicznych za pomoc膮 for await...of
P臋tla for await...of zapewnia czyst膮 i czyteln膮 sk艂adni臋 do konsumowania iterator贸w asynchronicznych. Automatycznie iteruje po warto艣ciach zwracanych przez iterator i czeka na rozwi膮zanie ka偶dej obietnicy przed wykonaniem cia艂a p臋tli. Upraszcza to kod asynchroniczny, czyni膮c go 艂atwiejszym do czytania i utrzymania. Ta funkcja promuje czystsze, bardziej czytelne przep艂ywy pracy asynchronicznej na ca艂ym 艣wiecie.
Oto przyk艂ad u偶ycia for await...of z generatorem asynchronicznym z poprzedniego przyk艂adu:
async function* myAsyncGenerator(data) {
for (let i = 0; i < data.length; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate async operation
yield data[i];
}
}
async function consumeGenerator() {
for await (const value of myAsyncGenerator([1, 2, 3])) {
console.log(value); // Output: 1, 2, 3 (with a 500ms delay between each)
}
}
consumeGenerator();
P臋tla for await...of sprawia, 偶e proces iteracji asynchronicznej jest znacznie prostszy i 艂atwiejszy do zrozumienia.
Przypadki u偶ycia iterator贸w asynchronicznych
Iteratory asynchroniczne s膮 niezwykle wszechstronne i mog膮 by膰 stosowane w r贸偶nych scenariuszach, w kt贸rych wymagane jest asynchroniczne przetwarzanie danych. Oto kilka popularnych przypadk贸w u偶ycia:
1. Odczytywanie du偶ych plik贸w
Podczas pracy z du偶ymi plikami, wczytywanie ca艂ego pliku do pami臋ci naraz mo偶e by膰 nieefektywne i zasoboch艂onne. Iteratory asynchroniczne umo偶liwiaj膮 odczytywanie pliku w porcjach asynchronicznie, przetwarzaj膮c ka偶d膮 porcj臋, gdy staje si臋 dost臋pna. Jest to szczeg贸lnie kluczowe dla aplikacji po stronie serwera i 艣rodowisk 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 processFile(filePath) {
for await (const line of readLines(filePath)) {
console.log(`Line: ${line}`);
// Process each line asynchronously
}
}
// Example usage
// processFile('path/to/large/file.txt');
W tym przyk艂adzie funkcja readLines odczytuje plik linia po linii asynchronicznie, zwracaj膮c ka偶d膮 lini臋 do wywo艂uj膮cego. Funkcja processFile nast臋pnie konsumuje linie i przetwarza je asynchronicznie.
2. Pobieranie danych z API
Podczas pobierania danych z API, zw艂aszcza w przypadku paginacji lub du偶ych zbior贸w danych, iteratory asynchroniczne mog膮 by膰 u偶ywane do pobierania i przetwarzania danych w porcjach. Pozwala to unikn膮膰 艂adowania ca艂ego zbioru danych do pami臋ci naraz i przetwarza膰 go przyrostowo. Zapewnia to responsywno艣膰 nawet przy du偶ych zbiorach danych, poprawiaj膮c do艣wiadczenia u偶ytkownika przy r贸偶nych pr臋dko艣ciach internetu i w r贸偶nych regionach.
async function* fetchPaginatedData(url) {
let nextUrl = url;
while (nextUrl) {
const response = await fetch(nextUrl);
const data = await response.json();
for (const item of data.results) {
yield item;
}
nextUrl = data.next;
}
}
async function processData() {
for await (const item of fetchPaginatedData('https://api.example.com/data')) {
console.log(item);
// Process each item asynchronously
}
}
// Example usage
// processData();
W tym przyk艂adzie funkcja fetchPaginatedData pobiera dane z paginowanego punktu ko艅cowego API, zwracaj膮c ka偶dy element do wywo艂uj膮cego. Funkcja processData nast臋pnie konsumuje elementy i przetwarza je asynchronicznie.
3. Obs艂uga strumieni danych w czasie rzeczywistym
Iteratory asynchroniczne s膮 r贸wnie偶 dobrze przystosowane do obs艂ugi strumieni danych w czasie rzeczywistym, takich jak te z WebSocket贸w lub zdarze艅 wysy艂anych przez serwer (server-sent events). Pozwalaj膮 na przetwarzanie przychodz膮cych danych w miar臋 ich nap艂ywania, bez blokowania g艂贸wnego w膮tku. Jest to kluczowe dla budowania responsywnych i skalowalnych aplikacji czasu rzeczywistego, niezb臋dnych dla us艂ug wymagaj膮cych aktualizacji co do sekundy.
async function* processWebSocketStream(socket) {
while (true) {
const message = await new Promise((resolve, reject) => {
socket.onmessage = (event) => {
resolve(event.data);
};
socket.onerror = (error) => {
reject(error);
};
});
yield message;
}
}
async function consumeWebSocketStream(socket) {
for await (const message of processWebSocketStream(socket)) {
console.log(`Received message: ${message}`);
// Process each message asynchronously
}
}
// Example usage
// const socket = new WebSocket('ws://example.com/socket');
// consumeWebSocketStream(socket);
W tym przyk艂adzie funkcja processWebSocketStream nas艂uchuje na wiadomo艣ci z po艂膮czenia WebSocket i zwraca ka偶d膮 wiadomo艣膰 do wywo艂uj膮cego. Funkcja consumeWebSocketStream nast臋pnie konsumuje wiadomo艣ci i przetwarza je asynchronicznie.
4. Architektury sterowane zdarzeniami
Iteratory asynchroniczne mog膮 by膰 zintegrowane z architekturami sterowanymi zdarzeniami w celu asynchronicznego przetwarzania zdarze艅. Pozwala to budowa膰 systemy, kt贸re reaguj膮 na zdarzenia w czasie rzeczywistym, bez blokowania g艂贸wnego w膮tku. Architektury sterowane zdarzeniami s膮 kluczowe dla nowoczesnych, skalowalnych aplikacji, kt贸re musz膮 szybko reagowa膰 na dzia艂ania u偶ytkownika lub zdarzenia systemowe.
const EventEmitter = require('events');
async function* eventStream(emitter, eventName) {
while (true) {
const value = await new Promise(resolve => {
emitter.once(eventName, resolve);
});
yield value;
}
}
async function consumeEventStream(emitter, eventName) {
for await (const event of eventStream(emitter, eventName)) {
console.log(`Received event: ${event}`);
// Process each event asynchronously
}
}
// Example usage
// const myEmitter = new EventEmitter();
// consumeEventStream(myEmitter, 'data');
// myEmitter.emit('data', 'Event data 1');
// myEmitter.emit('data', 'Event data 2');
Ten przyk艂ad tworzy asynchroniczny iterator, kt贸ry nas艂uchuje na zdarzenia emitowane przez EventEmitter. Ka偶de zdarzenie jest zwracane do konsumenta, co pozwala na asynchroniczne przetwarzanie zdarze艅. Integracja z architekturami sterowanymi zdarzeniami pozwala na tworzenie modularnych i reaktywnych system贸w.
Zalety u偶ywania iterator贸w asynchronicznych
Iteratory asynchroniczne oferuj膮 kilka zalet w por贸wnaniu z tradycyjnymi technikami programowania asynchronicznego, co czyni je cennym narz臋dziem w nowoczesnym tworzeniu aplikacji w JavaScript. Te zalety bezpo艣rednio przyczyniaj膮 si臋 do tworzenia bardziej wydajnych, responsywnych i skalowalnych aplikacji.
1. Poprawiona wydajno艣膰
Przetwarzaj膮c dane w porcjach asynchronicznie, iteratory asynchroniczne mog膮 poprawi膰 wydajno艣膰 aplikacji intensywnie przetwarzaj膮cych dane. Unikaj膮 one 艂adowania ca艂ego zbioru danych do pami臋ci naraz, co zmniejsza zu偶ycie pami臋ci i poprawia responsywno艣膰. Jest to szczeg贸lnie wa偶ne dla aplikacji pracuj膮cych z du偶ymi zbiorami danych lub strumieniami danych w czasie rzeczywistym, zapewniaj膮c ich wydajno艣膰 pod obci膮偶eniem.
2. Zwi臋kszona responsywno艣膰
Iteratory asynchroniczne pozwalaj膮 na przetwarzanie danych bez blokowania g艂贸wnego w膮tku, zapewniaj膮c, 偶e aplikacja pozostaje responsywna na interakcje u偶ytkownika. Jest to szczeg贸lnie wa偶ne w przypadku aplikacji internetowych, gdzie responsywny interfejs u偶ytkownika jest kluczowy dla dobrych do艣wiadcze艅 u偶ytkownika. Globalni u偶ytkownicy o r贸偶nej pr臋dko艣ci internetu doceni膮 responsywno艣膰 aplikacji.
3. Uproszczony kod asynchroniczny
Iteratory asynchroniczne, w po艂膮czeniu z p臋tl膮 for await...of, zapewniaj膮 czyst膮 i czyteln膮 sk艂adni臋 do pracy z asynchronicznymi strumieniami danych. To sprawia, 偶e kod asynchroniczny jest 艂atwiejszy do zrozumienia i utrzymania, zmniejszaj膮c prawdopodobie艅stwo b艂臋d贸w. Uproszczona sk艂adnia pozwala deweloperom skupi膰 si臋 na logice swoich aplikacji, a nie na z艂o偶ono艣ci programowania asynchronicznego.
4. Obs艂uga przeciwci艣nienia (backpressure)
Iteratory asynchroniczne naturalnie wspieraj膮 obs艂ug臋 przeciwci艣nienia (backpressure), czyli zdolno艣膰 do kontrolowania tempa, w jakim dane s膮 produkowane i konsumowane. Jest to wa偶ne, aby zapobiec przeci膮偶eniu aplikacji przez nap艂yw danych. Pozwalaj膮c konsumentom sygnalizowa膰 producentom, kiedy s膮 gotowi na wi臋cej danych, iteratory asynchroniczne mog膮 pom贸c w zapewnieniu, 偶e aplikacja pozostaje stabilna i wydajna pod du偶ym obci膮偶eniem. Przeciwci艣nienie jest szczeg贸lnie wa偶ne przy pracy ze strumieniami danych w czasie rzeczywistym lub przy przetwarzaniu du偶ych ilo艣ci danych, zapewniaj膮c stabilno艣膰 systemu.
Najlepsze praktyki u偶ywania iterator贸w asynchronicznych
Aby w pe艂ni wykorzysta膰 iteratory asynchroniczne, wa偶ne jest przestrzeganie kilku najlepszych praktyk. Te wytyczne pomog膮 zapewni膰, 偶e kod b臋dzie wydajny, 艂atwy w utrzymaniu i solidny.
1. Prawid艂owa obs艂uga b艂臋d贸w
Podczas pracy z operacjami asynchronicznymi wa偶ne jest prawid艂owe obs艂u偶enie b艂臋d贸w, aby zapobiec awarii aplikacji. U偶ywaj blok贸w try...catch do przechwytywania wszelkich b艂臋d贸w, kt贸re mog膮 wyst膮pi膰 podczas iteracji asynchronicznej. Prawid艂owa obs艂uga b艂臋d贸w zapewnia, 偶e aplikacja pozostaje stabilna nawet w przypadku napotkania nieoczekiwanych problem贸w, co przyczynia si臋 do bardziej solidnego do艣wiadczenia u偶ytkownika.
async function consumeGenerator() {
try {
for await (const value of myAsyncGenerator([1, 2, 3])) {
console.log(value);
}
} catch (error) {
console.error(`An error occurred: ${error}`);
// Handle the error
}
}
2. Unikaj operacji blokuj膮cych
Upewnij si臋, 偶e operacje asynchroniczne s膮 naprawd臋 nieblokuj膮ce. Unikaj wykonywania d艂ugotrwa艂ych operacji synchronicznych wewn膮trz iterator贸w asynchronicznych, poniewa偶 mo偶e to zniweczy膰 korzy艣ci p艂yn膮ce z przetwarzania asynchronicznego. Operacje nieblokuj膮ce zapewniaj膮, 偶e g艂贸wny w膮tek pozostaje responsywny, co zapewnia lepsze do艣wiadczenie u偶ytkownika, szczeg贸lnie w aplikacjach internetowych.
3. Ogranicz wsp贸艂bie偶no艣膰
Podczas pracy z wieloma iteratorami asynchronicznymi, nale偶y uwa偶a膰 na liczb臋 wsp贸艂bie偶nych operacji. Ograniczenie wsp贸艂bie偶no艣ci mo偶e zapobiec przeci膮偶eniu aplikacji przez zbyt wiele jednoczesnych zada艅. Jest to szczeg贸lnie wa偶ne przy pracy z operacjami zasoboch艂onnymi lub w 艣rodowiskach o ograniczonych zasobach. Pomaga to unika膰 problem贸w, takich jak wyczerpanie pami臋ci i spadek wydajno艣ci.
4. Zwalniaj zasoby
Po zako艅czeniu pracy z iteratorem asynchronicznym, upewnij si臋, 偶e zwolniono wszystkie zasoby, z kt贸rych m贸g艂 korzysta膰, takie jak uchwyty plik贸w czy po艂膮czenia sieciowe. Mo偶e to pom贸c w zapobieganiu wyciekom zasob贸w i poprawi膰 og贸ln膮 stabilno艣膰 aplikacji. Prawid艂owe zarz膮dzanie zasobami jest kluczowe dla d艂ugo dzia艂aj膮cych aplikacji lub us艂ug, zapewniaj膮c ich stabilno艣膰 w czasie.
5. U偶ywaj generator贸w asynchronicznych do z艂o偶onej logiki
W przypadku bardziej z艂o偶onej logiki iteracyjnej, generatory asynchroniczne zapewniaj膮 czystszy i 艂atwiejszy w utrzymaniu spos贸b definiowania iterator贸w asynchronicznych. Pozwalaj膮 one na u偶ycie s艂owa kluczowego yield do wstrzymywania i wznawiania funkcji generatora, co u艂atwia rozumowanie o przep艂ywie sterowania. Generatory asynchroniczne s膮 szczeg贸lnie przydatne, gdy logika iteracyjna obejmuje wiele krok贸w asynchronicznych lub rozga艂臋zienia warunkowe.
Iteratory asynchroniczne a obserwowalne (Observables)
Zar贸wno iteratory asynchroniczne, jak i obserwowalne (Observables) s膮 wzorcami do obs艂ugi asynchronicznych strumieni danych, ale maj膮 r贸偶ne cechy i przypadki u偶ycia.
Iteratory asynchroniczne
- Pull-based: Konsument jawnie 偶膮da nast臋pnej warto艣ci od iteratora.
- Pojedyncza subskrypcja: Ka偶dy iterator mo偶e by膰 skonsumowany tylko raz.
- Wbudowane wsparcie w JavaScript: Iteratory asynchroniczne i
for await...ofs膮 cz臋艣ci膮 specyfikacji j臋zyka.
Obserwowalne (Observables)
- Push-based: Producent wypycha warto艣ci do konsumenta.
- Wiele subskrypcji: Obserwowalne mo偶e by膰 subskrybowane przez wielu konsument贸w.
- Wymagaj膮 biblioteki: Obserwowalne s膮 zazwyczaj implementowane przy u偶yciu biblioteki, takiej jak RxJS.
Iteratory asynchroniczne s膮 dobrze dopasowane do scenariuszy, w kt贸rych konsument musi kontrolowa膰 tempo przetwarzania danych, na przyk艂ad podczas odczytywania du偶ych plik贸w lub pobierania danych z paginowanych API. Obserwowalne s膮 lepiej dopasowane do scenariuszy, w kt贸rych producent musi wypycha膰 dane do wielu konsument贸w, na przyk艂ad w przypadku strumieni danych w czasie rzeczywistym lub architektur sterowanych zdarzeniami. Wyb贸r mi臋dzy iteratorami asynchronicznymi a obserwowalnymi zale偶y od konkretnych potrzeb i wymaga艅 aplikacji.
Podsumowanie
Wzorzec asynchronicznego iteratora w JavaScript dostarcza pot臋偶ne i eleganckie rozwi膮zanie do obs艂ugi asynchronicznych strumieni danych. Przetwarzaj膮c dane w porcjach asynchronicznie, iteratory asynchroniczne mog膮 poprawi膰 wydajno艣膰 i responsywno艣膰 aplikacji. W po艂膮czeniu z p臋tl膮 for await...of i generatorami asynchronicznymi, zapewniaj膮 czyst膮 i czyteln膮 sk艂adni臋 do pracy z danymi asynchronicznymi. Post臋puj膮c zgodnie z najlepszymi praktykami opisanymi w tym wpisie, mo偶esz w pe艂ni wykorzysta膰 potencja艂 iterator贸w asynchronicznych do budowania wydajnych, 艂atwych w utrzymaniu i solidnych aplikacji.
Niezale偶nie od tego, czy pracujesz z du偶ymi plikami, pobierasz dane z API, obs艂ugujesz strumienie danych w czasie rzeczywistym, czy budujesz architektury sterowane zdarzeniami, iteratory asynchroniczne mog膮 pom贸c w pisaniu lepszego kodu asynchronicznego. Przyjmij ten wzorzec, aby poszerzy膰 swoje umiej臋tno艣ci programowania w JavaScript i tworzy膰 bardziej wydajne i responsywne aplikacje dla globalnej publiczno艣ci.