Odkryj JavaScript Async Iterator Helpers, aby zrewolucjonizować przetwarzanie strumieni. Naucz się efektywnie obsługiwać asynchroniczne strumienie danych za pomocą map, filter, take, drop i innych.
JavaScript Async Iterator Helpers: Wydajne przetwarzanie strumieniowe dla nowoczesnych aplikacji
W nowoczesnym programowaniu w JavaScript, obsługa asynchronicznych strumieni danych jest częstym wymogiem. Niezależnie od tego, czy pobierasz dane z API, przetwarzasz duże pliki, czy obsługujesz zdarzenia w czasie rzeczywistym, efektywne zarządzanie danymi asynchronicznymi jest kluczowe. JavaScript Async Iterator Helpers zapewniają potężny i elegancki sposób na przetwarzanie tych strumieni, oferując funkcyjne i kompozycyjne podejście do manipulacji danymi.
Czym są Async Iterators i Async Iterables?
Zanim zagłębimy się w Async Iterator Helpers, zrozummy podstawowe pojęcia: Async Iterators i Async Iterables.
Async Iterable to obiekt, który definiuje sposób asynchronicznego iterowania po swoich wartościach. Robi to poprzez implementację metody @@asyncIterator
, która zwraca Async Iterator.
Async Iterator to obiekt, który dostarcza metodę next()
. Metoda ta zwraca obietnicę (promise), która rozwiązuje się do obiektu z dwiema właściwościami:
value
: Następna wartość w sekwencji.done
: Wartość logiczna (boolean) wskazująca, czy sekwencja została w pełni przetworzona.
Oto prosty przykład:
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // Symulacja operacji asynchronicznej
yield i;
}
}
const asyncIterable = generateSequence(5);
(async () => {
for await (const value of asyncIterable) {
console.log(value); // Wynik: 1, 2, 3, 4, 5 (z 500ms opóźnieniem między każdym)
}
})();
W tym przykładzie generateSequence
to asynchroniczna funkcja generatora, która produkuje sekwencję liczb asynchronicznie. Pętla for await...of
jest używana do konsumowania wartości z asynchronicznego obiektu iterowalnego.
Wprowadzenie do Async Iterator Helpers
Async Iterator Helpers rozszerzają funkcjonalność Async Iterators, dostarczając zestaw metod do transformacji, filtrowania i manipulowania asynchronicznymi strumieniami danych. Umożliwiają one funkcyjny i kompozycyjny styl programowania, ułatwiając budowanie złożonych potoków przetwarzania danych.
Główne metody Async Iterator Helpers to:
map()
: Transformuje każdy element strumienia.filter()
: Wybiera elementy ze strumienia na podstawie warunku.take()
: Zwraca N pierwszych elementów strumienia.drop()
: Pomija N pierwszych elementów strumienia.toArray()
: Zbiera wszystkie elementy strumienia do tablicy.forEach()
: Wykonuje podaną funkcję raz dla każdego elementu strumienia.some()
: Sprawdza, czy co najmniej jeden element spełnia podany warunek.every()
: Sprawdza, czy wszystkie elementy spełniają podany warunek.find()
: Zwraca pierwszy element, który spełnia podany warunek.reduce()
: Stosuje funkcję względem akumulatora i każdego elementu, aby zredukować go do pojedynczej wartości.
Przeanalizujmy każdą z tych metod na przykładach.
map()
Metoda map()
transformuje każdy element asynchronicznego obiektu iterowalnego przy użyciu podanej funkcji. Zwraca nowy asynchroniczny obiekt iterowalny z przekształconymi wartościami.
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(5);
const doubledIterable = asyncIterable.map(x => x * 2);
(async () => {
for await (const value of doubledIterable) {
console.log(value); // Wynik: 2, 4, 6, 8, 10 (z 100ms opóźnieniem)
}
})();
W tym przykładzie map(x => x * 2)
podwaja każdą liczbę w sekwencji.
filter()
Metoda filter()
wybiera elementy z asynchronicznego obiektu iterowalnego na podstawie podanego warunku (funkcji predykatywnej). Zwraca nowy asynchroniczny obiekt iterowalny zawierający tylko te elementy, które spełniają warunek.
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(10);
const evenNumbersIterable = asyncIterable.filter(x => x % 2 === 0);
(async () => {
for await (const value of evenNumbersIterable) {
console.log(value); // Wynik: 2, 4, 6, 8, 10 (z 100ms opóźnieniem)
}
})();
W tym przykładzie filter(x => x % 2 === 0)
wybiera tylko parzyste liczby z sekwencji.
take()
Metoda take()
zwraca N pierwszych elementów z asynchronicznego obiektu iterowalnego. Zwraca nowy asynchroniczny obiekt iterowalny zawierający tylko określoną liczbę elementów.
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(5);
const firstThreeIterable = asyncIterable.take(3);
(async () => {
for await (const value of firstThreeIterable) {
console.log(value); // Wynik: 1, 2, 3 (z 100ms opóźnieniem)
}
})();
W tym przykładzie take(3)
wybiera trzy pierwsze liczby z sekwencji.
drop()
Metoda drop()
pomija N pierwszych elementów z asynchronicznego obiektu iterowalnego i zwraca resztę. Zwraca nowy asynchroniczny obiekt iterowalny zawierający pozostałe elementy.
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(5);
const afterFirstTwoIterable = asyncIterable.drop(2);
(async () => {
for await (const value of afterFirstTwoIterable) {
console.log(value); // Wynik: 3, 4, 5 (z 100ms opóźnieniem)
}
})();
W tym przykładzie drop(2)
pomija dwie pierwsze liczby z sekwencji.
toArray()
Metoda toArray()
przetwarza cały asynchroniczny obiekt iterowalny i zbiera wszystkie elementy do tablicy. Zwraca obietnicę, która rozwiązuje się do tablicy zawierającej wszystkie elementy.
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(5);
(async () => {
const numbersArray = await asyncIterable.toArray();
console.log(numbersArray); // Wynik: [1, 2, 3, 4, 5]
})();
W tym przykładzie toArray()
zbiera wszystkie liczby z sekwencji do tablicy.
forEach()
Metoda forEach()
wykonuje podaną funkcję raz dla każdego elementu w asynchronicznym obiekcie iterowalnym. *Nie* zwraca nowego asynchronicznego obiektu iterowalnego, wykonuje funkcję dla jej efektów ubocznych. Może to być przydatne do wykonywania operacji takich jak logowanie lub aktualizacja interfejsu użytkownika.
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(3);
(async () => {
await asyncIterable.forEach(value => {
console.log("Value:", value);
});
console.log("forEach completed");
})();
// Wynik: Value: 1, Value: 2, Value: 3, forEach completed
some()
Metoda some()
sprawdza, czy co najmniej jeden element w asynchronicznym obiekcie iterowalnym przechodzi test zaimplementowany w podanej funkcji. Zwraca obietnicę, która rozwiązuje się do wartości logicznej (true
, jeśli co najmniej jeden element spełnia warunek, w przeciwnym razie false
).
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(5);
(async () => {
const hasEvenNumber = await asyncIterable.some(x => x % 2 === 0);
console.log("Has even number:", hasEvenNumber); // Wynik: Has even number: true
})();
every()
Metoda every()
sprawdza, czy wszystkie elementy w asynchronicznym obiekcie iterowalnym przechodzą test zaimplementowany w podanej funkcji. Zwraca obietnicę, która rozwiązuje się do wartości logicznej (true
, jeśli wszystkie elementy spełniają warunek, w przeciwnym razie false
).
async function* generateSequence(end) {
for (let i = 2; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(4);
(async () => {
const areAllEven = await asyncIterable.every(x => x % 2 === 0);
console.log("Are all even:", areAllEven); // Wynik: Are all even: true
})();
find()
Metoda find()
zwraca pierwszy element w asynchronicznym obiekcie iterowalnym, który spełnia podaną funkcję testującą. Jeśli żadna wartość nie spełnia funkcji testującej, zwracane jest undefined
. Zwraca obietnicę, która rozwiązuje się do znalezionego elementu lub undefined
.
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(5);
(async () => {
const firstEven = await asyncIterable.find(x => x % 2 === 0);
console.log("First even number:", firstEven); // Wynik: First even number: 2
})();
reduce()
Metoda reduce()
wykonuje dostarczoną przez użytkownika funkcję zwrotną ("reducer") na każdym elemencie asynchronicznego obiektu iterowalnego, w kolejności, przekazując wartość zwrotną z obliczenia na poprzednim elemencie. Ostatecznym wynikiem uruchomienia reducera na wszystkich elementach jest pojedyncza wartość. Zwraca obietnicę, która rozwiązuje się do ostatecznej skumulowanej wartości.
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(5);
(async () => {
const sum = await asyncIterable.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log("Sum:", sum); // Wynik: Sum: 15
})();
Praktyczne przykłady i przypadki użycia
Async Iterator Helpers są cenne w różnych scenariuszach. Przyjrzyjmy się kilku praktycznym przykładom:
1. Przetwarzanie danych z API strumieniowego
Wyobraź sobie, że budujesz pulpit nawigacyjny do wizualizacji danych w czasie rzeczywistym, który otrzymuje dane z API strumieniowego. API wysyła aktualizacje w sposób ciągły, a Ty musisz przetwarzać te aktualizacje, aby wyświetlić najnowsze informacje.
async function* fetchDataFromAPI(url) {
let response = await fetch(url);
if (!response.body) {
throw new Error("ReadableStream nie jest obsługiwany w tym środowisku");
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
const chunk = decoder.decode(value);
// Zakładając, że API wysyła obiekty JSON oddzielone znakami nowej linii
const lines = chunk.split('\n');
for (const line of lines) {
if (line.trim() !== '') {
yield JSON.parse(line);
}
}
}
} finally {
reader.releaseLock();
}
}
const apiURL = 'https://example.com/streaming-api'; // Zastąp swoim adresem URL API
const dataStream = fetchDataFromAPI(apiURL);
// Przetwarzaj strumień danych
(async () => {
for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
console.log('Przetworzone dane:', data);
// Zaktualizuj pulpit nawigacyjny przetworzonymi danymi
}
})();
W tym przykładzie fetchDataFromAPI
pobiera dane z API strumieniowego, parsuje obiekty JSON i udostępnia je jako asynchroniczny obiekt iterowalny. Metoda filter
wybiera tylko metryki, a metoda map
przekształca dane do pożądanego formatu przed aktualizacją pulpitu nawigacyjnego.
2. Odczytywanie i przetwarzanie dużych plików
Załóżmy, że musisz przetworzyć duży plik CSV zawierający dane klientów. Zamiast ładować cały plik do pamięci, możesz użyć Async Iterator Helpers, aby przetwarzać go kawałek po kawałku.
async function* readLinesFromFile(filePath) {
const file = await fsPromises.open(filePath, 'r');
try {
let buffer = Buffer.alloc(1024);
let fileOffset = 0;
let remainder = '';
while (true) {
const { bytesRead } = await file.read(buffer, 0, buffer.length, fileOffset);
if (bytesRead === 0) {
if (remainder) {
yield remainder;
}
break;
}
fileOffset += bytesRead;
const chunk = buffer.toString('utf8', 0, bytesRead);
const lines = chunk.split('\n');
lines[0] = remainder + lines[0];
remainder = lines.pop() || '';
for (const line of lines) {
yield line;
}
}
} finally {
await file.close();
}
}
const filePath = './customer_data.csv'; // Zastąp ścieżką do swojego pliku
const lines = readLinesFromFile(filePath);
// Przetwarzaj linie
(async () => {
for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
console.log('Klient z USA:', customerData);
// Przetwarzaj dane klientów z USA
}
})();
W tym przykładzie readLinesFromFile
odczytuje plik linia po linii i udostępnia każdą linię jako asynchroniczny obiekt iterowalny. Metoda drop(1)
pomija wiersz nagłówka, metoda map
dzieli linię na kolumny, a metoda filter
wybiera tylko klientów z USA.
3. Obsługa zdarzeń w czasie rzeczywistym
Async Iterator Helpers mogą być również używane do obsługi zdarzeń w czasie rzeczywistym ze źródeł takich jak WebSockets. Możesz utworzyć asynchroniczny obiekt iterowalny, który emituje zdarzenia w miarę ich nadejścia, a następnie użyć metod pomocniczych do przetwarzania tych zdarzeń.
async function* createWebSocketStream(url) {
const ws = new WebSocket(url);
yield new Promise((resolve, reject) => {
ws.onopen = () => {
resolve();
};
ws.onerror = (error) => {
reject(error);
};
});
try {
while (ws.readyState === WebSocket.OPEN) {
yield new Promise((resolve, reject) => {
ws.onmessage = (event) => {
resolve(JSON.parse(event.data));
};
ws.onerror = (error) => {
reject(error);
};
ws.onclose = () => {
resolve(null); // Rozstrzygnij z wartością null, gdy połączenie zostanie zamknięte
}
});
}
} finally {
ws.close();
}
}
const websocketURL = 'wss://example.com/events'; // Zastąp swoim adresem URL WebSocket
const eventStream = createWebSocketStream(websocketURL);
// Przetwarzaj strumień zdarzeń
(async () => {
for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
console.log('Zdarzenie logowania użytkownika:', event);
// Przetwarzaj zdarzenie logowania użytkownika
}
})();
W tym przykładzie createWebSocketStream
tworzy asynchroniczny obiekt iterowalny, który emituje zdarzenia otrzymane z WebSocket. Metoda filter
wybiera tylko zdarzenia logowania użytkownika, a metoda map
przekształca dane do pożądanego formatu.
Korzyści z używania Async Iterator Helpers
- Poprawiona czytelność i łatwość utrzymania kodu: Async Iterator Helpers promują funkcyjny i kompozycyjny styl programowania, sprawiając, że kod jest łatwiejszy do czytania, zrozumienia i utrzymania. Łańcuchowy charakter metod pomocniczych pozwala na wyrażanie złożonych potoków przetwarzania danych w zwięzły i deklaratywny sposób.
- Efektywne wykorzystanie pamięci: Async Iterator Helpers przetwarzają strumienie danych leniwie, co oznacza, że przetwarzają dane tylko w razie potrzeby. Może to znacznie zmniejszyć zużycie pamięci, zwłaszcza w przypadku dużych zbiorów danych lub ciągłych strumieni danych.
- Zwiększona wydajność: Przetwarzając dane w strumieniu, Async Iterator Helpers mogą poprawić wydajność, unikając konieczności jednoczesnego ładowania całego zbioru danych do pamięci. Może to być szczególnie korzystne dla aplikacji obsługujących duże pliki, dane w czasie rzeczywistym lub API strumieniowe.
- Uproszczone programowanie asynchroniczne: Async Iterator Helpers abstrahują od złożoności programowania asynchronicznego, ułatwiając pracę z asynchronicznymi strumieniami danych. Nie musisz ręcznie zarządzać obietnicami ani wywołaniami zwrotnymi; metody pomocnicze obsługują operacje asynchroniczne za kulisami.
- Komponowalny i reużywalny kod: Async Iterator Helpers są zaprojektowane tak, aby można je było komponować, co oznacza, że można je łatwo łączyć w łańcuchy, tworząc złożone potoki przetwarzania danych. Promuje to ponowne wykorzystanie kodu i ogranicza jego duplikację.
Wsparcie w przeglądarkach i środowiskach uruchomieniowych
Async Iterator Helpers to wciąż stosunkowo nowa funkcja w JavaScript. Pod koniec 2024 roku znajdują się one w 3. etapie procesu standaryzacji TC39, co oznacza, że prawdopodobnie zostaną wkrótce znormalizowane. Jednak nie są one jeszcze natywnie obsługiwane we wszystkich przeglądarkach i wersjach Node.js.
Wsparcie w przeglądarkach: Nowoczesne przeglądarki, takie jak Chrome, Firefox, Safari i Edge, stopniowo dodają wsparcie dla Async Iterator Helpers. Możesz sprawdzić najnowsze informacje o kompatybilności przeglądarek na stronach takich jak Can I use..., aby zobaczyć, które przeglądarki obsługują tę funkcję.
Wsparcie w Node.js: Najnowsze wersje Node.js (v18 i nowsze) zapewniają eksperymentalne wsparcie dla Async Iterator Helpers. Aby z nich skorzystać, może być konieczne uruchomienie Node.js z flagą --experimental-async-iterator
.
Polyfille: Jeśli musisz używać Async Iterator Helpers w środowiskach, które natywnie ich nie obsługują, możesz użyć polyfilla. Polyfill to fragment kodu, który zapewnia brakującą funkcjonalność. Dostępnych jest kilka bibliotek polyfill dla Async Iterator Helpers; popularną opcją jest biblioteka core-js
.
Implementacja niestandardowych asynchronicznych iteratorów
Chociaż Async Iterator Helpers zapewniają wygodny sposób na przetwarzanie istniejących asynchronicznych obiektów iterowalnych, czasami może być konieczne stworzenie własnych niestandardowych iteratorów asynchronicznych. Pozwala to na obsługę danych z różnych źródeł, takich jak bazy danych, API czy systemy plików, w sposób strumieniowy.
Aby utworzyć niestandardowy iterator asynchroniczny, musisz zaimplementować metodę @@asyncIterator
na obiekcie. Metoda ta powinna zwracać obiekt z metodą next()
. Metoda next()
powinna zwracać obietnicę, która rozwiązuje się do obiektu z właściwościami value
i done
.
Oto przykład niestandardowego iteratora asynchronicznego, który pobiera dane z paginowanego API:
async function* fetchPaginatedData(baseURL) {
let page = 1;
let hasMore = true;
while (hasMore) {
const url = `${baseURL}?page=${page}`;
const response = await fetch(url);
const data = await response.json();
if (data.results.length === 0) {
hasMore = false;
break;
}
for (const item of data.results) {
yield item;
}
page++;
}
}
const apiBaseURL = 'https://api.example.com/data'; // Zastąp swoim adresem URL API
const paginatedData = fetchPaginatedData(apiBaseURL);
// Przetwarzaj dane paginowane
(async () => {
for await (const item of paginatedData) {
console.log('Element:', item);
// Przetwarzaj element
}
})();
W tym przykładzie fetchPaginatedData
pobiera dane z paginowanego API, udostępniając każdy element w miarę jego pobierania. Iterator asynchroniczny obsługuje logikę paginacji, ułatwiając konsumowanie danych w sposób strumieniowy.
Potencjalne wyzwania i uwagi
Chociaż Async Iterator Helpers oferują liczne korzyści, ważne jest, aby być świadomym pewnych potencjalnych wyzwań i uwag:
- Obsługa błędów: Prawidłowa obsługa błędów jest kluczowa podczas pracy z asynchronicznymi strumieniami danych. Musisz obsługiwać potencjalne błędy, które mogą wystąpić podczas pobierania, przetwarzania lub transformacji danych. Używanie bloków
try...catch
i technik obsługi błędów w ramach metod pomocniczych iteratora asynchronicznego jest niezbędne. - Anulowanie: W niektórych scenariuszach może być konieczne anulowanie przetwarzania asynchronicznego obiektu iterowalnego, zanim zostanie on w pełni przetworzony. Może to być przydatne w przypadku długotrwałych operacji lub strumieni danych w czasie rzeczywistym, gdzie chcesz przerwać przetwarzanie po spełnieniu określonego warunku. Implementacja mechanizmów anulowania, takich jak użycie
AbortController
, może pomóc w skutecznym zarządzaniu operacjami asynchronicznymi. - Backpressure: W przypadku strumieni danych, które produkują dane szybciej, niż mogą być one konsumowane, problemem staje się backpressure (przeciwciśnienie). Backpressure odnosi się do zdolności konsumenta do sygnalizowania producentowi, aby spowolnił tempo emitowania danych. Implementacja mechanizmów backpressure może zapobiec przepełnieniu pamięci i zapewnić efektywne przetwarzanie strumienia danych.
- Debugowanie: Debugowanie kodu asynchronicznego może być trudniejsze niż debugowanie kodu synchronicznego. Podczas pracy z Async Iterator Helpers ważne jest, aby używać narzędzi i technik debugowania do śledzenia przepływu danych przez potok i identyfikowania potencjalnych problemów.
Dobre praktyki korzystania z Async Iterator Helpers
Aby w pełni wykorzystać Async Iterator Helpers, rozważ następujące dobre praktyki:
- Używaj opisowych nazw zmiennych: Wybieraj opisowe nazwy zmiennych, które jasno wskazują cel każdego asynchronicznego obiektu iterowalnego i metody pomocniczej. Ułatwi to czytanie i zrozumienie kodu.
- Utrzymuj funkcje pomocnicze zwięzłymi: Staraj się, aby funkcje przekazywane do Async Iterator Helpers były jak najbardziej zwięzłe i skoncentrowane. Unikaj wykonywania złożonych operacji w tych funkcjach; zamiast tego twórz oddzielne funkcje dla złożonej logiki.
- Łącz metody w łańcuchy dla czytelności: Łącz Async Iterator Helpers w łańcuchy, aby stworzyć przejrzysty i deklaratywny potok przetwarzania danych. Unikaj nadmiernego zagnieżdżania metod, ponieważ może to utrudnić czytanie kodu.
- Elegancko obsługuj błędy: Zaimplementuj odpowiednie mechanizmy obsługi błędów, aby przechwytywać i obsługiwać potencjalne błędy, które mogą wystąpić podczas przetwarzania danych. Podawaj informacyjne komunikaty o błędach, aby pomóc w diagnozowaniu i rozwiązywaniu problemów.
- Dokładnie testuj swój kod: Dokładnie testuj swój kod, aby upewnić się, że poprawnie obsługuje różne scenariusze. Pisz testy jednostkowe, aby zweryfikować zachowanie poszczególnych metod pomocniczych, oraz testy integracyjne, aby zweryfikować cały potok przetwarzania danych.
Zaawansowane techniki
Komponowanie niestandardowych metod pomocniczych
Możesz tworzyć własne niestandardowe metody pomocnicze iteratora asynchronicznego, komponując istniejące metody lub budując nowe od podstaw. Pozwala to dostosować funkcjonalność do konkretnych potrzeb i tworzyć komponenty wielokrotnego użytku.
async function* takeWhile(asyncIterable, predicate) {
for await (const value of asyncIterable) {
if (!predicate(value)) {
break;
}
yield value;
}
}
// Przykład użycia:
async function* generateSequence(end) {
for (let i = 1; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i;
}
}
const asyncIterable = generateSequence(10);
const firstFive = takeWhile(asyncIterable, x => x <= 5);
(async () => {
for await (const value of firstFive) {
console.log(value);
}
})();
Łączenie wielu asynchronicznych obiektów iterowalnych
Możesz łączyć wiele asynchronicznych obiektów iterowalnych w jeden, używając technik takich jak zip
lub merge
. Pozwala to na jednoczesne przetwarzanie danych z wielu źródeł.
async function* zip(asyncIterable1, asyncIterable2) {
const iterator1 = asyncIterable1[Symbol.asyncIterator]();
const iterator2 = asyncIterable2[Symbol.asyncIterator]();
while (true) {
const result1 = await iterator1.next();
const result2 = await iterator2.next();
if (result1.done || result2.done) {
break;
}
yield [result1.value, result2.value];
}
}
// Przykład użycia:
async function* generateSequence1(end) {
for (let i = 1; i <= end; i++) {
yield i;
}
}
async function* generateSequence2(end) {
for (let i = 10; i <= end + 9; i++) {
yield i;
}
}
const iterable1 = generateSequence1(5);
const iterable2 = generateSequence2(5);
(async () => {
for await (const [value1, value2] of zip(iterable1, iterable2)) {
console.log(value1, value2);
}
})();
Podsumowanie
JavaScript Async Iterator Helpers zapewniają potężny i elegancki sposób przetwarzania asynchronicznych strumieni danych. Oferują one funkcyjne i kompozycyjne podejście do manipulacji danymi, ułatwiając budowanie złożonych potoków przetwarzania danych. Rozumiejąc podstawowe koncepcje Async Iterators i Async Iterables oraz opanowując różne metody pomocnicze, możesz znacznie poprawić wydajność i łatwość utrzymania swojego asynchronicznego kodu JavaScript. W miarę jak wsparcie w przeglądarkach i środowiskach uruchomieniowych będzie rosło, Async Iterator Helpers staną się niezbędnym narzędziem dla nowoczesnych programistów JavaScript.