Slovenčina

Objavte pomocníkov pre asynchrónne iterátory v JavaScripte, ktoré prinášajú revolúciu do spracovania streamov. Naučte sa efektívne narábať s asynchrónnymi dátovými streamami pomocou map, filter, take, drop a ďalších.

Pomocníci pre asynchrónne iterátory v JavaScripte: Výkonné spracovanie streamov pre moderné aplikácie

V modernom vývoji JavaScriptu je práca s asynchrónnymi dátovými streamami bežnou požiadavkou. Či už načítavate dáta z API, spracovávate veľké súbory alebo riešite udalosti v reálnom čase, efektívne spravovanie asynchrónnych dát je kľúčové. Pomocníci pre asynchrónne iterátory v JavaScripte poskytujú výkonný a elegantný spôsob spracovania týchto streamov, ponúkajúc funkcionálny a skladateľný prístup k manipulácii s dátami.

Čo sú asynchrónne iterátory a asynchrónne iterovateľné objekty?

Predtým, ako sa ponoríme do pomocníkov pre asynchrónne iterátory, poďme si vysvetliť základné pojmy: asynchrónne iterátory a asynchrónne iterovateľné objekty.

Asynchrónny iterovateľný objekt (Async Iterable) je objekt, ktorý definuje spôsob asynchrónneho iterovania cez svoje hodnoty. Robí to implementáciou metódy @@asyncIterator, ktorá vracia asynchrónny iterátor.

Asynchrónny iterátor (Async Iterator) je objekt, ktorý poskytuje metódu next(). Táto metóda vracia promise, ktorá sa resolvne na objekt s dvoma vlastnosťami:

Tu je jednoduchý príklad:


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 500)); // Simulácia asynchrónnej operácie
    yield i;
  }
}

const asyncIterable = generateSequence(5);

(async () => {
  for await (const value of asyncIterable) {
    console.log(value); // Výstup: 1, 2, 3, 4, 5 (s oneskorením 500ms medzi každým)
  }
})();

V tomto príklade je generateSequence asynchrónna generátorová funkcia, ktorá produkuje sekvenciu čísel asynchrónne. Cyklus for await...of sa používa na spracovanie hodnôt z asynchrónneho iterovateľného objektu.

Predstavenie pomocníkov pre asynchrónne iterátory

Pomocníci pre asynchrónne iterátory rozširujú funkcionalitu asynchrónnych iterátorov a poskytujú súbor metód na transformáciu, filtrovanie a manipuláciu s asynchrónnymi dátovými streamami. Umožňujú funkcionálny a skladateľný štýl programovania, čo uľahčuje budovanie zložitých dátových spracovateľských kanálov.

Medzi hlavných pomocníkov pre asynchrónne iterátory patria:

Pozrime sa na každého pomocníka s príkladmi.

map()

Pomocník map() transformuje každý prvok asynchrónneho iterovateľného objektu pomocou poskytnutej funkcie. Vracia nový asynchrónny iterovateľný objekt s transformovanými hodnotami.


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); // Výstup: 2, 4, 6, 8, 10 (s oneskorením 100ms)
  }
})();

V tomto príklade map(x => x * 2) zdvojnásobí každé číslo v sekvencii.

filter()

Pomocník filter() vyberá prvky z asynchrónneho iterovateľného objektu na základe poskytnutej podmienky (predikátovej funkcie). Vracia nový asynchrónny iterovateľný objekt obsahujúci iba prvky, ktoré spĺňajú podmienku.


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); // Výstup: 2, 4, 6, 8, 10 (s oneskorením 100ms)
  }
})();

V tomto príklade filter(x => x % 2 === 0) vyberie zo sekvencie iba párne čísla.

take()

Pomocník take() vráti prvých N prvkov z asynchrónneho iterovateľného objektu. Vracia nový asynchrónny iterovateľný objekt obsahujúci iba zadaný počet prvkov.


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); // Výstup: 1, 2, 3 (s oneskorením 100ms)
  }
})();

V tomto príklade take(3) vyberie prvé tri čísla zo sekvencie.

drop()

Pomocník drop() preskočí prvých N prvkov z asynchrónneho iterovateľného objektu a vráti zvyšok. Vracia nový asynchrónny iterovateľný objekt obsahujúci zostávajúce prvky.


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); // Výstup: 3, 4, 5 (s oneskorením 100ms)
  }
})();

V tomto príklade drop(2) preskočí prvé dve čísla zo sekvencie.

toArray()

Pomocník toArray() spracuje celý asynchrónny iterovateľný objekt a zozbiera všetky prvky do poľa. Vracia promise, ktorá sa resolvne na pole obsahujúce všetky prvky.


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); // Výstup: [1, 2, 3, 4, 5]
})();

V tomto príklade toArray() zozbiera všetky čísla zo sekvencie do poľa.

forEach()

Pomocník forEach() vykoná poskytnutú funkciu raz pre každý prvok v asynchrónnom iterovateľnom objekte. *Nevracia* nový asynchrónny iterovateľný objekt, vykonáva funkciu s vedľajšími účinkami. To môže byť užitočné pre operácie ako logovanie alebo aktualizácia používateľského rozhrania.


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");
})();
// Výstup: Value: 1, Value: 2, Value: 3, forEach completed

some()

Pomocník some() testuje, či aspoň jeden prvok v asynchrónnom iterovateľnom objekte prejde testom implementovaným poskytnutou funkciou. Vracia promise, ktorá sa resolvne na booleovskú hodnotu (true, ak aspoň jeden prvok spĺňa podmienku, inak 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); // Výstup: Has even number: true
})();

every()

Pomocník every() testuje, či všetky prvky v asynchrónnom iterovateľnom objekte prejdú testom implementovaným poskytnutou funkciou. Vracia promise, ktorá sa resolvne na booleovskú hodnotu (true, ak všetky prvky spĺňajú podmienku, inak 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); // Výstup: Are all even: true
})();

find()

Pomocník find() vráti prvý prvok v asynchrónnom iterovateľnom objekte, ktorý spĺňa poskytnutú testovaciu funkciu. Ak žiadna hodnota nespĺňa testovaciu funkciu, vráti sa undefined. Vracia promise, ktorá sa resolvne na nájdený prvok alebo 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); // Výstup: First even number: 2
})();

reduce()

Pomocník reduce() vykoná používateľom zadanú spätnú „reducer“ funkciu na každom prvku asynchrónneho iterovateľného objektu v poradí, pričom odovzdáva návratovú hodnotu z výpočtu na predchádzajúcom prvku. Konečným výsledkom spustenia reducera na všetkých prvkoch je jedna hodnota. Vracia promise, ktorá sa resolvne na konečnú akumulovanú hodnotu.


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); // Výstup: Sum: 15
})();

Praktické príklady a prípady použitia

Pomocníci pre asynchrónne iterátory sú cenní v rôznych scenároch. Pozrime sa na niekoľko praktických príkladov:

1. Spracovanie dát zo streamovacieho API

Predstavte si, že budujete vizualizačný panel s dátami v reálnom čase, ktorý prijíma dáta zo streamovacieho API. API posiela aktualizácie nepretržite a vy potrebujete tieto aktualizácie spracovať, aby ste zobrazili najnovšie informácie.


async function* fetchDataFromAPI(url) {
  let response = await fetch(url);

  if (!response.body) {
    throw new Error("ReadableStream not supported in this environment");
  }

  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);
      // Predpokladáme, že API posiela JSON objekty oddelené novými riadkami
      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'; // Nahraďte URL vášho API
const dataStream = fetchDataFromAPI(apiURL);

// Spracovanie dátového streamu
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('Processed Data:', data);
    // Aktualizácia panelu so spracovanými dátami
  }
})();

V tomto príklade fetchDataFromAPI načítava dáta zo streamovacieho API, parsuje JSON objekty a poskytuje ich ako asynchrónny iterovateľný objekt. Pomocník filter vyberá iba metriky a pomocník map transformuje dáta do požadovaného formátu pred aktualizáciou panelu.

2. Čítanie a spracovanie veľkých súborov

Predpokladajme, že potrebujete spracovať veľký CSV súbor obsahujúci údaje o zákazníkoch. Namiesto načítania celého súboru do pamäte môžete použiť pomocníkov pre asynchrónne iterátory na jeho spracovanie po častiach (chunk by chunk).


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'; // Nahraďte cestou k vášmu súboru
const lines = readLinesFromFile(filePath);

// Spracovanie riadkov
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Customer from USA:', customerData);
    // Spracovanie údajov o zákazníkoch z USA
  }
})();

V tomto príklade readLinesFromFile číta súbor riadok po riadku a poskytuje každý riadok ako asynchrónny iterovateľný objekt. Pomocník drop(1) preskočí hlavičkový riadok, pomocník map rozdelí riadok na stĺpce a pomocník filter vyberie iba zákazníkov z USA.

3. Spracovanie udalostí v reálnom čase

Pomocníci pre asynchrónne iterátory môžu byť tiež použití na spracovanie udalostí v reálnom čase zo zdrojov ako sú WebSockets. Môžete vytvoriť asynchrónny iterovateľný objekt, ktorý emituje udalosti, ako prichádzajú, a potom použiť pomocníkov na spracovanie týchto udalostí.


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); // Resolvne s null, keď sa spojenie uzavrie
        }
      });

    }
  } finally {
    ws.close();
  }
}

const websocketURL = 'wss://example.com/events'; // Nahraďte URL vášho WebSocketu
const eventStream = createWebSocketStream(websocketURL);

// Spracovanie streamu udalostí
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('User Login Event:', event);
    // Spracovanie udalosti prihlásenia používateľa
  }
})();

V tomto príklade createWebSocketStream vytvára asynchrónny iterovateľný objekt, ktorý emituje udalosti prijaté z WebSocketu. Pomocník filter vyberá iba udalosti prihlásenia používateľa a pomocník map transformuje dáta do požadovaného formátu.

Výhody používania pomocníkov pre asynchrónne iterátory

Podpora v prehliadačoch a runtime prostrediach

Pomocníci pre asynchrónne iterátory sú v JavaScripte stále relatívne novou funkciou. Koncom roka 2024 sú v 3. fáze štandardizačného procesu TC39, čo znamená, že je pravdepodobné, že budú v blízkej budúcnosti štandardizované. Zatiaľ však nie sú natívne podporované vo všetkých prehliadačoch a verziách Node.js.

Podpora v prehliadačoch: Moderné prehliadače ako Chrome, Firefox, Safari a Edge postupne pridávajú podporu pre pomocníkov pre asynchrónne iterátory. Najnovšie informácie o kompatibilite prehliadačov si môžete overiť na webových stránkach ako Can I use..., aby ste zistili, ktoré prehliadače túto funkciu podporujú.

Podpora v Node.js: Novšie verzie Node.js (v18 a vyššie) poskytujú experimentálnu podporu pre pomocníkov pre asynchrónne iterátory. Na ich použitie možno budete musieť spustiť Node.js s príznakom --experimental-async-iterator.

Polyfilly: Ak potrebujete použiť pomocníkov pre asynchrónne iterátory v prostrediach, ktoré ich natívne nepodporujú, môžete použiť polyfill. Polyfill je kus kódu, ktorý poskytuje chýbajúcu funkcionalitu. K dispozícii je niekoľko polyfill knižníc pre pomocníkov pre asynchrónne iterátory; populárnou možnosťou je knižnica core-js.

Implementácia vlastných asynchrónnych iterátorov

Hoci pomocníci pre asynchrónne iterátory poskytujú pohodlný spôsob spracovania existujúcich asynchrónnych iterovateľných objektov, niekedy možno budete potrebovať vytvoriť si vlastné. To vám umožní spracovávať dáta z rôznych zdrojov, ako sú databázy, API alebo súborové systémy, streamovacím spôsobom.

Na vytvorenie vlastného asynchrónneho iterátora musíte na objekte implementovať metódu @@asyncIterator. Táto metóda by mala vrátiť objekt s metódou next(). Metóda next() by mala vrátiť promise, ktorá sa resolvne na objekt s vlastnosťami value a done.

Tu je príklad vlastného asynchrónneho iterátora, ktorý načítava dáta zo stránkovaného 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'; // Nahraďte URL vášho API
const paginatedData = fetchPaginatedData(apiBaseURL);

// Spracovanie stránkovaných dát
(async () => {
  for await (const item of paginatedData) {
    console.log('Item:', item);
    // Spracovanie položky
  }
})();

V tomto príklade fetchPaginatedData načítava dáta zo stránkovaného API a poskytuje každú položku, ako je získaná. Asynchrónny iterátor sa stará o logiku stránkovania, čo uľahčuje spracovanie dát streamovacím spôsobom.

Potenciálne výzvy a úvahy

Hoci pomocníci pre asynchrónne iterátory ponúkajú množstvo výhod, je dôležité si byť vedomý niektorých potenciálnych výziev a úvah:

Osvedčené postupy pre používanie pomocníkov pre asynchrónne iterátory

Aby ste z pomocníkov pre asynchrónne iterátory vyťažili čo najviac, zvážte nasledujúce osvedčené postupy:

Pokročilé techniky

Skladanie vlastných pomocníkov

Môžete si vytvoriť vlastných pomocníkov pre asynchrónne iterátory skladaním existujúcich pomocníkov alebo ich budovaním od nuly. To vám umožní prispôsobiť funkcionalitu vašim špecifickým potrebám a vytvárať znovupoužiteľné komponenty.


async function* takeWhile(asyncIterable, predicate) {
  for await (const value of asyncIterable) {
    if (!predicate(value)) {
      break;
    }
    yield value;
  }
}

// Príklad použitia:
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);
  }
})();

Kombinovanie viacerých asynchrónnych iterovateľných objektov

Môžete skombinovať viacero asynchrónnych iterovateľných objektov do jedného pomocou techník ako zip alebo merge. To vám umožní spracovávať dáta z viacerých zdrojov súčasne.


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];
    }
}

// Príklad použitia:
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);
    }
})();

Záver

Pomocníci pre asynchrónne iterátory v JavaScripte poskytujú výkonný a elegantný spôsob spracovania asynchrónnych dátových streamov. Ponúkajú funkcionálny a skladateľný prístup k manipulácii s dátami, čo uľahčuje budovanie zložitých dátových spracovateľských kanálov. Porozumením základným konceptom asynchrónnych iterátorov a asynchrónnych iterovateľných objektov a zvládnutím rôznych pomocných metód môžete výrazne zlepšiť efektivitu a udržiavateľnosť vášho asynchrónneho JavaScript kódu. S rastúcou podporou v prehliadačoch a runtime prostrediach sú pomocníci pre asynchrónne iterátory pripravení stať sa nevyhnutným nástrojom pre moderných vývojárov JavaScriptu.