Čeština

Prozkoumejte pomocníky pro asynchronní iterátory v JavaScriptu pro revoluční zpracování streamů. Naučte se efektivně pracovat s datovými proudy pomocí map, filter, take, drop a dalších.

Pomocníci pro asynchronní iterátory v JavaScriptu: Výkonné zpracování streamů pro moderní aplikace

V moderním vývoji v JavaScriptu je práce s asynchronními datovými proudy běžným požadavkem. Ať už načítáte data z API, zpracováváte velké soubory nebo obsluhujete události v reálném čase, efektivní správa asynchronních dat je klíčová. Pomocníci pro asynchronní iterátory v JavaScriptu poskytují výkonný a elegantní způsob, jak tyto proudy zpracovávat, a nabízejí funkcionální a skládatelný přístup k manipulaci s daty.

Co jsou asynchronní iterátory a asynchronní iterovatelné objekty?

Než se ponoříme do pomocníků pro asynchronní iterátory, pojďme si vysvětlit základní koncepty: asynchronní iterátory a asynchronní iterovatelné objekty.

Asynchronní iterovatelný objekt (Async Iterable) je objekt, který definuje způsob, jak asynchronně procházet jeho hodnoty. Dělá to implementací metody @@asyncIterator, která vrací asynchronní iterátor (Async Iterator).

Asynchronní iterátor (Async Iterator) je objekt, který poskytuje metodu next(). Tato metoda vrací promise, která se resolvuje na objekt se dvěma vlastnostmi:

Zde je jednoduchý příklad:


async function* generateSequence(end) {
  for (let i = 1; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 500)); // Simulace asynchronní operace
    yield i;
  }
}

const asyncIterable = generateSequence(5);

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

V tomto příkladu je generateSequence asynchronní generátorová funkce, která asynchronně vytváří sekvenci čísel. Smyčka for await...of se používá ke spotřebování hodnot z asynchronního iterovatelného objektu.

Představení pomocníků pro asynchronní iterátory

Pomocníci pro asynchronní iterátory rozšiřují funkcionalitu asynchronních iterátorů a poskytují sadu metod pro transformaci, filtrování a manipulaci s asynchronními datovými proudy. Umožňují funkcionální a skládatelný styl programování, což usnadňuje tvorbu složitých pipeline pro zpracování dat.

Mezi hlavní pomocníky pro asynchronní iterátory patří:

Pojďme se podívat na každého pomocníka s příklady.

map()

Pomocník map() transformuje každý prvek asynchronního iterovatelného objektu pomocí zadané funkce. Vrací nový asynchronní iterovatelný 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 (se 100ms zpožděním)
  }
})();

V tomto příkladu map(x => x * 2) zdvojnásobí každé číslo v sekvenci.

filter()

Pomocník filter() vybírá prvky z asynchronního iterovatelného objektu na základě zadané podmínky (predikátové funkce). Vrací nový asynchronní iterovatelný objekt obsahující pouze prvky, které splňují podmínku.


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 (se 100ms zpožděním)
  }
})();

V tomto příkladu filter(x => x % 2 === 0) vybere pouze sudá čísla ze sekvence.

take()

Pomocník take() vrací prvních N prvků z asynchronního iterovatelného objektu. Vrací nový asynchronní iterovatelný objekt obsahující pouze zadaný počet prvků.


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 (se 100ms zpožděním)
  }
})();

V tomto příkladu take(3) vybere první tři čísla ze sekvence.

drop()

Pomocník drop() přeskočí prvních N prvků z asynchronního iterovatelného objektu a vrátí zbytek. Vrací nový asynchronní iterovatelný objekt obsahující zbývající 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 (se 100ms zpožděním)
  }
})();

V tomto příkladu drop(2) přeskočí první dvě čísla ze sekvence.

toArray()

Pomocník toArray() spotřebuje celý asynchronní iterovatelný objekt a shromáždí všechny prvky do pole. Vrací promise, která se resolvuje na pole obsahující všechny 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 příkladu toArray() shromáždí všechna čísla ze sekvence do pole.

forEach()

Pomocník forEach() provede zadanou funkci jednou pro každý prvek v asynchronním iterovatelném objektu. Nevrací nový asynchronní iterovatelný objekt, provádí funkci s vedlejšími účinky. To může být užitečné pro provádění operací, jako je logování nebo aktualizace UI.


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("Hodnota:", value);
  });
  console.log("forEach dokončeno");
})();
// Výstup: Hodnota: 1, Hodnota: 2, Hodnota: 3, forEach dokončeno

some()

Pomocník some() testuje, zda alespoň jeden prvek v asynchronním iterovatelném objektu projde testem implementovaným zadanou funkcí. Vrací promise, která se resolvuje na booleovskou hodnotu (true, pokud alespoň jeden prvek splňuje podmínku, jinak 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("Obsahuje sudé číslo:", hasEvenNumber); // Výstup: Obsahuje sudé číslo: true
})();

every()

Pomocník every() testuje, zda všechny prvky v asynchronním iterovatelném objektu projdou testem implementovaným zadanou funkcí. Vrací promise, která se resolvuje na booleovskou hodnotu (true, pokud všechny prvky splňují podmínku, jinak 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("Jsou všechna sudá:", areAllEven); // Výstup: Jsou všechna sudá: true
})();

find()

Pomocník find() vrací první prvek v asynchronním iterovatelném objektu, který splňuje zadanou testovací funkci. Pokud žádná hodnota nesplňuje testovací funkci, je vráceno undefined. Vrací promise, která se resolvuje na nalezený prvek nebo 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("První sudé číslo:", firstEven); // Výstup: První sudé číslo: 2
})();

reduce()

Pomocník reduce() provádí uživatelem zadanou „reducer“ callback funkci na každém prvku asynchronního iterovatelného objektu v pořadí, přičemž předává návratovou hodnotu z výpočtu na předchozím prvku. Konečným výsledkem spuštění reduceru na všech prvcích je jedna hodnota. Vrací promise, která se resolvuje na konečnou akumulovanou 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("Součet:", sum); // Výstup: Součet: 15
})();

Praktické příklady a případy použití

Pomocníci pro asynchronní iterátory jsou cenní v různých scénářích. Podívejme se na několik praktických příkladů:

1. Zpracování dat ze streamovacího API

Představte si, že vytváříte dashboard pro vizualizaci dat v reálném čase, který přijímá data ze streamovacího API. API posílá aktualizace nepřetržitě a vy je potřebujete zpracovat, abyste zobrazili nejnovější informace.


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

  if (!response.body) {
    throw new Error("ReadableStream není v tomto prostředí podporován");
  }

  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);
      // Za předpokladu, že API posílá JSON objekty oddělené novými řádky
      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 vaší URL adresou API
const dataStream = fetchDataFromAPI(apiURL);

// Zpracování datového proudu
(async () => {
  for await (const data of dataStream.filter(item => item.type === 'metric').map(item => ({ timestamp: item.timestamp, value: item.value }))) {
    console.log('Zpracovaná data:', data);
    // Aktualizace dashboardu zpracovanými daty
  }
})();

V tomto příkladu fetchDataFromAPI načítá data ze streamovacího API, parsuje JSON objekty a vrací je jako asynchronní iterovatelný objekt. Pomocník filter vybere pouze metriky a pomocník map transformuje data do požadovaného formátu před aktualizací dashboardu.

2. Čtení a zpracování velkých souborů

Předpokládejme, že potřebujete zpracovat velký CSV soubor obsahující zákaznická data. Místo načítání celého souboru do paměti můžete použít pomocníky pro asynchronní iterátory ke zpracování po částech.


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 vašemu souboru
const lines = readLinesFromFile(filePath);

// Zpracování řádků
(async () => {
  for await (const customerData of lines.drop(1).map(line => line.split(',')).filter(data => data[2] === 'USA')) {
    console.log('Zákazník z USA:', customerData);
    // Zpracování dat zákazníků z USA
  }
})();

V tomto příkladu readLinesFromFile čte soubor řádek po řádku a vrací každý řádek jako asynchronní iterovatelný objekt. Pomocník drop(1) přeskočí hlavičkový řádek, pomocník map rozdělí řádek na sloupce a pomocník filter vybere pouze zákazníky z USA.

3. Zpracování událostí v reálném čase

Pomocníci pro asynchronní iterátory mohou být také použiti ke zpracování událostí v reálném čase ze zdrojů, jako jsou WebSockets. Můžete vytvořit asynchronní iterovatelný objekt, který emituje události, jakmile přijdou, a poté použít pomocníky k jejich zpracování.


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); // Resolve s hodnotou null při uzavření spojení
        }
      });

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

const websocketURL = 'wss://example.com/events'; // Nahraďte vaší WebSocket URL
const eventStream = createWebSocketStream(websocketURL);

// Zpracování proudu událostí
(async () => {
  for await (const event of eventStream.filter(event => event.type === 'user_login').map(event => ({ userId: event.userId, timestamp: event.timestamp }))) {
    console.log('Událost přihlášení uživatele:', event);
    // Zpracování události přihlášení uživatele
  }
})();

V tomto příkladu createWebSocketStream vytváří asynchronní iterovatelný objekt, který emituje události přijaté z WebSocketu. Pomocník filter vybere pouze události přihlášení uživatele a pomocník map transformuje data do požadovaného formátu.

Výhody používání pomocníků pro asynchronní iterátory

Podpora v prohlížečích a běhových prostředích

Pomocníci pro asynchronní iterátory jsou v JavaScriptu stále relativně novou funkcí. Ke konci roku 2024 jsou ve fázi 3 standardizačního procesu TC39, což znamená, že je pravděpodobné, že budou v blízké budoucnosti standardizováni. Zatím však nejsou nativně podporováni ve všech prohlížečích a verzích Node.js.

Podpora v prohlížečích: Moderní prohlížeče jako Chrome, Firefox, Safari a Edge postupně přidávají podporu pro pomocníky pro asynchronní iterátory. Nejnovější informace o kompatibilitě prohlížečů můžete zkontrolovat na webových stránkách jako Can I use..., abyste zjistili, které prohlížeče tuto funkci podporují.

Podpora v Node.js: Novější verze Node.js (v18 a vyšší) poskytují experimentální podporu pro pomocníky pro asynchronní iterátory. Abyste je mohli používat, možná budete muset spustit Node.js s příznakem --experimental-async-iterator.

Polyfilly: Pokud potřebujete používat pomocníky pro asynchronní iterátory v prostředích, která je nativně nepodporují, můžete použít polyfill. Polyfill je kousek kódu, který poskytuje chybějící funkcionalitu. K dispozici je několik knihoven polyfillů pro pomocníky pro asynchronní iterátory; populární volbou je knihovna core-js.

Implementace vlastních asynchronních iterátorů

I když pomocníci pro asynchronní iterátory poskytují pohodlný způsob zpracování existujících asynchronních iterovatelných objektů, někdy budete možná potřebovat vytvořit své vlastní. To vám umožní zpracovávat data z různých zdrojů, jako jsou databáze, API nebo souborové systémy, streamovacím způsobem.

Chcete-li vytvořit vlastní asynchronní iterátor, musíte na objektu implementovat metodu @@asyncIterator. Tato metoda by měla vrátit objekt s metodou next(). Metoda next() by měla vrátit promise, která se resolvuje na objekt s vlastnostmi value a done.

Zde je příklad vlastního asynchronního iterátoru, který načítá data z paginované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 vaší URL adresou API
const paginatedData = fetchPaginatedData(apiBaseURL);

// Zpracování paginovaných dat
(async () => {
  for await (const item of paginatedData) {
    console.log('Položka:', item);
    // Zpracování položky
  }
})();

V tomto příkladu fetchPaginatedData načítá data z paginovaného API a vrací každou položku, jakmile je načtena. Asynchronní iterátor se stará o logiku paginace, což usnadňuje konzumaci dat streamovacím způsobem.

Potenciální výzvy a úvahy

Ačkoli pomocníci pro asynchronní iterátory nabízejí řadu výhod, je důležité si být vědom některých potenciálních výzev a úvah:

Doporučené postupy pro používání pomocníků pro asynchronní iterátory

Abyste z pomocníků pro asynchronní iterátory vytěžili co nejvíce, zvažte následující doporučené postupy:

Pokročilé techniky

Skládání vlastních pomocníků

Můžete si vytvořit vlastní pomocníky pro asynchronní iterátory skládáním existujících pomocníků nebo jejich tvorbou od nuly. To vám umožní přizpůsobit funkcionalitu vašim specifickým potřebám a vytvářet znovupoužitelné komponenty.


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

// Příklad použití:
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);
  }
})();

Kombinování více asynchronních iterovatelných objektů

Můžete zkombinovat více asynchronních iterovatelných objektů do jednoho pomocí technik jako zip nebo merge. To vám umožní zpracovávat data z více zdrojů současně.


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

// Příklad použití:
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ávěr

Pomocníci pro asynchronní iterátory v JavaScriptu poskytují výkonný a elegantní způsob zpracování asynchronních datových proudů. Nabízejí funkcionální a skládatelný přístup k manipulaci s daty, což usnadňuje tvorbu složitých pipeline pro zpracování dat. Porozuměním základním konceptům asynchronních iterátorů a iterovatelných objektů a zvládnutím různých pomocných metod můžete výrazně zlepšit efektivitu a udržovatelnost vašeho asynchronního JavaScriptového kódu. S rostoucí podporou v prohlížečích a běhových prostředích jsou pomocníci pro asynchronní iterátory připraveni stát se nezbytným nástrojem pro moderní vývojáře v JavaScriptu.

Pomocníci pro asynchronní iterátory v JavaScriptu: Výkonné zpracování streamů pro moderní aplikace | MLOG