Navody

Asynchronní iterátory a generátory

Asynchronní iterátory umožňují iterovat přes data, která přicházejí asynchronně. Například když stahujeme něco po částech přes síť. Asynchronní generátory toto třídění ještě více usnadňují.

Podívejme se nejprve na jednoduchý příklad, abychom pochopili syntaxi, a poté na skutečný praktický příklad.

Asynchronní iterátory

Asynchronní iterátory jsou podobné běžným iterátorům, ale mají určité syntaktické rozdíly.

„Normální“ iterovatelný objekt, jak je podrobně popsáno v kapitole Iterovatelné objekty, vypadá přibližně takto:

let range = < from: 1, to: 5, // for..of вызывает этот метод один раз в самом начале [Symbol.iterator]() < // . возвращает объект-итератор: // далее for..of работает только с этим объектом, запрашивая следующее значение вызовом next() return < current: this.from, last: this.to, // next() вызывается на каждой итерации цикла for..of next() < // (2) // должен возвращать значение в виде объекта if (this.current ; >else < return < done: true >; > > >; > >; for(let value of range) < alert(value); // 1, потом 2, потом 3, потом 4, потом 5 >

V případě potřeby si prosím přečtěte kapitolu o iterátorech, která podrobně popisuje běžné iterátory.

Chcete-li objekt asynchronně iterovat:

  1. Použijte Symbol.asyncIterator místo Symbol.iterator .
  2. next() musí vrátit slib.
  3. Pro iterování nad takovým objektem použijte smyčku for await (let item of iterable).

Vytvořme iterovatelný objekt typu range, stejně jako v předchozím příkladu, ale nyní bude vracet hodnoty asynchronně, jednu za sekundu:

let range = < from: 1, to: 5, // for await..of вызывает этот метод один раз в самом начале [Symbol.asyncIterator]() < // (1) // . возвращает объект-итератор: // далее for await..of работает только с этим объектом, // запрашивая у него следующие значения вызовом next() return < current: this.from, last: this.to, // next() вызывается на каждой итерации цикла for await..of async next() < // (2) // должен возвращать значение как объект // (автоматически оборачивается в промис с помощью async) // можно использовать await внутри для асинхронности: await new Promise(resolve =>setTimeout(resolve, 1000)); // (3) if (this.current ; > else < return < done: true >; > > >; > >; (async () => < for await (let value of range) < // (4) alert(value); // 1,2,3,4,5 >>)()

Jak vidíme, struktura je podobná běžným iterátorům:

  1. Aby byl objekt asynchronně iterovatelný, musí mít metodu Symbol.asyncIterator(1).
  2. Tato metoda musí vracet objekt s metodou next(), která zase vrací promise (2).
  3. Metoda next() nemusí být async, může to být regulární metoda, která vrací promise, ale async nám umožňuje použít await, takže je to pohodlné. Zde se jen na sekundu (3) pozastavíme.
  4. Pro iteraci použijeme metodu for await (let value of range)(4), kde za „for“ přidáme „await“. Metoda jednou zavolá metodu range[Symbol.asyncIterator]() a poté její metodu next() pro získání hodnot.
Přečtěte si více
Tipy na množení rododendronů od holandského hobby zahradníka.

Zde je malý tahák:

Iterátory Asynchronní iterátory
Metoda pro vytvoření iterovatelného objektu Symbol.iterator Symbol.asyncIterator
next() vrací jakoukoli hodnotu slib
pro použití na kole pro. z pro čekání. na

Operátor rozložení nefunguje asynchronně.

Funkce, které vyžadují běžné synchronní iterátory, nefungují s asynchronními iterátory.

Například operátor šíření (tři tečky.) nebude fungovat:

alert( [. range] ); // Ошибка, нет Symbol.iterator

To je přirozené, protože očekává Symbol.iterator , stejně jako for..of bez await . Neodpovídá Symbol.asyncIterator .

Asynchronní generátory

Jak již víme, JavaScript má generátory a ty jsou iterovatelné.

Připomeňme si generátor sekvencí z kapitoly Generátory. Generuje sekvenci hodnot od začátku do konce:

function* generateSequence(start, end) < for (let i = start; i >for(let value of generateSequence(1, 5)) < alert(value); // 1, потом 2, потом 3, потом 4, потом 5 >

V běžných generátorech nemůžeme použít await. Všechny hodnoty musí přicházet synchronně: ve for..of není prostor pro zpoždění, jedná se o synchronní konstrukci.

Ale co když potřebujeme použít await v těle generátoru? Například k provádění síťových požadavků.

Žádný problém, stačí na začátek přidat async, takto:

async function* generateSequence(start, end) < for (let i = start; i setTimeout(resolve, 1000)); yield i; >> (async () => < let generator = generateSequence(1, 5); for await (let value of generator) < alert(value); // 1, потом 2, потом 3, потом 4, потом 5 >>)();

Nyní máme asynchronní generátor, který můžeme iterovat pomocí funkce for await . of .

Je to opravdu tak jednoduché. Přidáme klíčové slovo async a uvnitř generátoru nyní můžeme používat await , stejně jako promise a další asynchronní funkce.

Z technického hlediska je dalším rozdílem asynchronního generátoru to, že jeho metoda generator.next() je nyní také asynchronní a vrací promise.

Z běžného generátoru můžeme získat hodnoty pomocí result = generator.next(). Pro asynchronní generátor musíme přidat await , takto:

result = await generator.next(); // result = 

Asynchronně iterované objekty

Jak již víme, abychom objekt učinili iterovatelným, musíme k němu přidat Symbol.iterator.

let range = < from: 1, to: 5, [Symbol.iterator]() < return >>

Obvyklá praxe pro Symbol.iterator je vracet generátor spíše než jednoduchý objekt s next, jako v předchozím příkladu.

Připomeňme si příklad z kapitoly Generátory:

let range = < from: 1, to: 5, *[Symbol.iterator]() < // сокращение для [Symbol.iterator]: function*() for(let value = this.from; value >>; for(let value of range) < alert(value); // 1, потом 2, потом 3, потом 4, потом 5 >

Zde je vytvořený objekt range iterovatelný a generátor *[Symbol.iterator] implementuje logiku pro výčet hodnot.

Pokud chceme do generátoru přidat asynchronní akce, musíme nahradit Symbol.iterator asynchronním Symbol.asyncIterator:

let range = < from: 1, to: 5, async *[Symbol.asyncIterator]() < // то же, что и [Symbol.asyncIterator]: async function*() for(let value = this.from; value setTimeout(resolve, 1000)); yield value; >> >; (async () => < for await (let value of range) < alert(value); // 1, потом 2, потом 3, потом 4, потом 5 >>)();

Nyní se hodnoty objevují s jednosekundovým zpožděním mezi nimi.

Přečtěte si více
Erné lišky a čarodějnice: 5 podivných, ale jedlých hub z kaliningradských lesů - Kaliningrad News

Příklad z reálné praxe

Zatím jsme viděli jednoduché příklady pro základní představu. Nyní se podívejme na reálnou situaci.

Existuje mnoho online služeb, které poskytují data ve stránkované podobě. Například když potřebujeme seznam uživatelů, požadavek vrátí předdefinovaný počet (např. 100) uživatelů – „jedna stránka“ a URL adresu další stránky.

Tento přístup je velmi běžný, a to nejen pro uživatele, ale pro cokoli. Například GitHub umožňuje získat commity tímto způsobem, rozdělené podle stránek:

  • Musíte odeslat požadavek na URL adresu ve tvaru https://api.github.com/repos//commits .
  • Odpovědí bude JSON s 30 commity a odkazem na další stránku v záhlaví Link.
  • Tuto referenci pak můžete použít pro další pull request, abyste získali další dávku commitů a tak dále.

Ale samozřejmě místo této složité interakce bychom chtěli mít jen objekt s commity, přes které můžeme iterovat, takto:

let repo = 'javascript-tutorial/en.javascript.info'; // репозиторий на GitHub, откуда брать коммиты for await (let commit of fetchCommits(repo)) < // обработка коммитов >

Chtěli bychom vytvořit funkci fetchCommits(repo), která bude načítat commity dotazováním, kdykoli bude potřeba. A necháme ji, aby se starala o veškeré stránkování, pro nás to bude jen await..of .

S asynchronními generátory je to docela snadno implementovatelné:

async function* fetchCommits(repo) < let url = `https://api.github.com/repos/$/commits`; while (url) < const response = await fetch(url, < // (1) headers: , // GitHub требует заголовок user-agent >); const body = await response.json(); // (2) ответ в формате JSON (массив коммитов) // (3) Ссылка на следующую страницу находится в заголовках, извлекаем её let nextPage = response.headers.get('Link').match(/<(.*?)>; rel="next"/); nextPage = nextPage && nextPage[1]; url = nextPage; for(let commit of body) < // (4) вернуть коммиты один за другим, до окончания страницы yield commit; >> >
  1. Pro stahování ze vzdálené adresy URL používáme metodu fetch prohlížeče. To umožňuje v případě potřeby přidat autorizaci a další hlavičky, zde GitHub vyžaduje User-Agent.
  2. Výsledek načtení je zpracován jako JSON, což je opět metoda nativní pro fetch.
  3. Potřebujeme získat URL adresu další stránky z hlavičky odpovědi Link. Má speciální formát, takže používáme regulární výraz. URL adresa další stránky může vypadat například takto: https://api.github.com/repositories/93253246/commits?page=2 , generuje ji samotný GitHub.
  4. Pak vypíšeme všechny přijaté commity a po jejich dokončení se spustí další iterace while(url), která provede další požadavek.

Příklad použití (zobrazuje autory commitu v konzoli):

(async () => < let count = 0; for await (const commit of fetchCommits('javascript-tutorial/en.javascript.info')) < console.log(commit.author.login); if (++count == 100) < // остановимся на 100 коммитах break; >> >)();

Přesně tohle jsme chtěli. Vnitřní mechanismy stránkovaných požadavků nejsou zvenčí viditelné. Pro nás je to jen asynchronní generátor, který vrací commity.

Celkem

Běžné iterátory a generátory fungují skvěle s daty, jejichž vytvoření nebo načtení nezabere mnoho času.

Přečtěte si více
Odrůda hroznů Isabella: fotografie, recenze, popis, vlastnosti.

Pokud očekáváme, že data budou dorazit asynchronně, se zpožděním, můžeme použít jejich asynchronní protějšky a for await..of místo for..of.

Syntaktické rozdíly mezi asynchronními a regulárními iterátory:

Iterovatelný objekt Asynchronně iterováno
Metoda pro získání iterátoru Symbol.iterator Symbol.asyncIterator
next() vrací slib, který končí

Syntaktické rozdíly mezi asynchronními a regulárními generátory:

Generátory Asynchronní generátory
Reklama funkce* asynchronní funkce*
generátor.next() vrací slib, který končí

Ve webovém vývoji se často setkáváme s datovými toky, které přicházejí v kusech. Například stahování nebo nahrávání velkého souboru.

Pro zpracování takových dat můžeme použít asynchronní generátory. V některých prostředích, například v prohlížečích, existuje další API s názvem Streams, které poskytuje speciální rozhraní pro práci s takovými datovými proudy, jejich transformaci a přenos z jednoho proudu do druhého (například stahování z jednoho zdroje a okamžité odeslání do jiného).

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Back to top button