Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Ahoj všichni. V kontaktu s Omelnitským Sergejem. Není to tak dávno, co jsem hostoval stream o reaktivním programování, kde jsem mluvil o asynchronii v JavaScriptu. Dnes bych tento materiál rád shrnul.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Ale než se pustíme do hlavního materiálu, musíme udělat úvod. Začněme tedy definicemi: co je zásobník a fronta?

Zásobník je kolekce, jejíž prvky jsou získávány na základě LIFO „poslední dovnitř, první ven“.

Fronta je kolekce, jejíž prvky jsou získávány podle principu („first in, first out“ FIFO

Dobře, pokračujme.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

JavaScript je jednovláknový programovací jazyk. To znamená, že má pouze jedno vlákno provádění a jeden zásobník, kde jsou funkce řazeny do fronty ke spuštění. JavaScript tedy může provádět vždy pouze jednu operaci, zatímco ostatní operace počkají, až na ně přijde řada, dokud nebudou vyvolány.

Zásobník hovorů je datová struktura, která zjednodušeně řečeno zaznamenává informace o místě v programu, kde se nacházíme. Pokud skočíme do funkce, posuneme její vstup na vrchol zásobníku. Když se vrátíme z funkce, vyjmeme nejvyšší prvek ze zásobníku a skončíme tam, odkud jsme tuto funkci zavolali. To je vše, co zásobník dokáže. A teď velmi zajímavá otázka. Jak potom funguje asynchronie v JavaScriptu?

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Ve skutečnosti mají prohlížeče kromě zásobníku speciální frontu pro práci s tzv. WebAPI. Funkce z této fronty budou provedeny v pořadí až po úplném vymazání zásobníku. Teprve poté jsou umístěny z fronty do zásobníku k provedení. Pokud je na hromádce v tuto chvíli alespoň jeden prvek, nemohou se na hromádku dostat. Právě kvůli tomu je volání funkcí podle časového limitu často časově nepřesné, protože funkce se nemůže dostat z fronty do zásobníku, dokud je plný.

Podívejme se na následující příklad a pojďme si ho projít krok za krokem. Podívejme se také, co se děje v systému.

console.log('Hi');
setTimeout(function cb1() {
    console.log('cb1');
}, 5000);
console.log('Bye');

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

1) Zatím se nic neděje. Konzole prohlížeče je čistá, zásobník hovorů je prázdný.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

2) Poté je do zásobníku volání přidán příkaz console.log('Hi').

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

3) A je splněno

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

4) Poté se console.log('Hi') odstraní ze zásobníku volání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

5) Nyní přejdeme k příkazu setTimeout(funkce cb1() {… }). Je přidán do zásobníku volání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

6) Provede se příkaz setTimeout(funkce cb1() {… }). Prohlížeč vytvoří časovač, který je součástí webového rozhraní API. Provede odpočítávání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

7) Příkaz setTimeout(funkce cb1() {… }) dokončil svou práci a je odstraněn ze zásobníku volání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

8) Příkaz console.log('Bye') je přidán do zásobníku volání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

9) Provede se příkaz console.log('Nashledanou').

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

10) Příkaz console.log('Bye') je odstraněn ze zásobníku volání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

11) Po uplynutí alespoň 5000 ms časovač skončí a zařadí zpětné volání cb1 do fronty zpětných volání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

12) Smyčka událostí převezme funkci cb1 z fronty zpětných volání a vloží ji do zásobníku volání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

13) Funkce cb1 se provede a přidá do zásobníku volání console.log('cb1').

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

14) Provede se příkaz console.log('cb1').

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

15) Příkaz console.log('cb1') je odstraněn ze zásobníku volání.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

16) Funkce cb1 je odstraněna ze zásobníku volání.

Podívejme se na příklad v dynamice:

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

No, podívali jsme se na to, jak je asynchronie implementována v JavaScriptu. Pojďme si nyní krátce promluvit o vývoji asynchronního kódu.

Evoluce asynchronního kódu.

a(function (resultsFromA) {
    b(resultsFromA, function (resultsFromB) {
        c(resultsFromB, function (resultsFromC) {
            d(resultsFromC, function (resultsFromD) {
                e(resultsFromD, function (resultsFromE) {
                    f(resultsFromE, function (resultsFromF) {
                        console.log(resultsFromF);
                    })
                })
            })
        })
    })
});

Asynchronní programování, jak jej známe v JavaScriptu, lze provádět pouze pomocí funkcí. Mohou být předány jako jakákoli jiná proměnná jiným funkcím. Tak se zrodila zpětná volání. A je to cool, zábavné a vroucí, až se to změní ve smutek, melancholii a smutek. Proč? Ano, je to jednoduché:

  • Jak roste složitost kódu, projekt se rychle promění v nejasné vícenásobné vnořené bloky – „peklo zpětného volání“.
  • Ošetření chyb lze snadno přehlédnout.
  • Nemůžete vrátit výrazy s návratem.

S příchodem Promise se situace trochu zlepšila.

new Promise(function(resolve, reject) {
    setTimeout(() => resolve(1), 2000);

}).then((result) => {
    alert(result);
    return result + 2;

}).then((result) => {
    throw new Error('FAILED HERE');
    alert(result);
    return result + 2;

}).then((result) => {
    alert(result);
    return result + 2;

}).catch((e) => {
    console.log('error: ', e);
});

  • Objevily se slibové řetězce, které zlepšily čitelnost kódu
  • Existovala samostatná metoda zachycení chyb
  • Paralelní provádění s Promise.all přidáno
  • Vnořenou asynchronii můžeme vyřešit pomocí async/await

Ale slib má svá omezení. Například slib, bez tance s tamburínou, nelze zrušit a hlavně funguje s jednou hodnotou.

No a tady se plynule blížíme k reaktivnímu programování. Unavený? Dobrá věc je, že si můžete jít uvařit racky, zamyslet se nad tím a vrátit se a přečíst si víc. A budu pokračovat.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Reaktivní programování - programovací paradigma zaměřené na datové toky a šíření změn. Podívejme se blíže na to, co je to datový tok.

// Получаем ссылку на элемент
const input = ducument.querySelector('input');

const eventsArray = [];

// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
    event => eventsArray.push(event)
);

Představme si, že máme vstupní pole. Vytvoříme pole a pro každé sepsání vstupní události uložíme událost do našeho pole. Zároveň bych rád poznamenal, že naše pole je řazeno podle času, tzn. index pozdějších událostí je větší než index dřívějších událostí. Takové pole je zjednodušeným modelem toku dat, ale ještě to není tok. Aby se toto pole mohlo bezpečně nazývat stream, musí být schopné nějak informovat předplatitele, že do něj dorazila nová data. Tím se dostáváme k definici proudění.

Datový tok

const { interval } = Rx;
const { take } = RxOperators;

interval(1000).pipe(
    take(4)
)

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Tok je pole dat seřazených podle času, které může naznačovat, že se data změnila. Nyní si představte, jak pohodlné je psát kód, ve kterém musíte pro jednu akci spustit několik událostí v různých částech kódu. Jednoduše se přihlásíme k odběru streamu a ten nám řekne, kdy nastanou změny. A knihovna RxJs to umí.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

RxJS je knihovna pro práci s asynchronními a událostmi založenými programy pomocí pozorovatelných sekvencí. Knihovna poskytuje hlavní typ Pozorovatelné, několik typů pomocníků (Pozorovatelé, plánovači, subjekty) a operátory pro práci s událostmi jako s kolekcemi (mapa, filtr, zmenšení, každý a podobné z JavaScript Array).

Pojďme pochopit základní koncepty této knihovny.

Pozorovatelný, pozorovatel, producent

Observable je první základní typ, na který se podíváme. Tato třída obsahuje hlavní část implementace RxJs. Je spojen s pozorovatelným tokem, k jehož odběru se lze přihlásit pomocí metody odběru.

Observable implementuje pomocný mechanismus pro vytváření aktualizací, tzv Pozorovatel. Zdroj hodnot pro pozorovatele se nazývá Výrobce. Může to být pole, iterátor, webový soket, nějaký druh události atd. Můžeme tedy říci, že pozorovatelný je vodič mezi Producentem a Observerem.

Observable zpracovává tři druhy událostí pozorovatele:

  • další - nová data
  • error - chyba, pokud byla sekvence ukončena z důvodu výjimky. tato událost také znamená konec sekvence.
  • kompletní - signál o konci sekvence. To znamená, že již nebudou žádná nová data

Podívejme se na ukázku:

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Na začátku zpracujeme hodnoty 1, 2, 3 a po 1 sec. dostaneme 4 a ukončíme naše vlákno.

Přemýšlet nahlas

A pak jsem si uvědomil, že je zajímavější vyprávět, než o tom psát. 😀

Předplatné

Když se přihlásíme k odběru streamu, vytvoříme novou třídu předplatné, což nám dává možnost odhlásit se s metodou odhlásit. Pomocí metody můžeme také seskupovat odběry přidat. Je logické, že můžeme rozdělit vlákna pomocí odstranit. Metody přidání a odebrání přijímají jako vstup jiné předplatné. Chtěl bych poznamenat, že když se odhlásíme, odhlásíme se ze všech dětských odběrů, jako by také volali metodu odhlášení. Pokračuj.

Typy proudů

HOT
STUDENÝ

Producent je vytvořen mimo pozorovatelné
Producent je vytvořen uvnitř pozorovatelny

Data jsou předávána v okamžiku vytvoření pozorovatelného prvku
Údaje jsou poskytovány v době předplatného.

K odhlášení potřebujete více logiky
Vlákno se samo ukončí

Používá vztah jeden k mnoha
Používá vztah jedna ku jedné

Všechna předplatná mají stejnou hodnotu
Předplatné jsou nezávislé

Pokud neexistuje žádné předplatné, může dojít ke ztrátě dat
Znovu vydá všechny hodnoty streamu pro nové předplatné

Abych to přirovnal, představoval bych si horký stream jako film v kině. V jakém okamžiku jste přišli, od toho okamžiku jste se začali dívat. Studený proud bych přirovnal k volání v těch. Podpěra, podpora. Každý volající poslouchá záznam záznamníku od začátku do konce, ale můžete zavěsit a odhlásit se z odběru.

Podotýkám, že existují i ​​tzv. teplé proudy (s takovou definicí jsem se setkal extrémně zřídka a pouze v cizích komunitách) - jedná se o proud přecházející ze studeného proudu na horký. Nabízí se otázka - kde použít)) Uvedu příklad z praxe.

Pracuji s Angular. Aktivně používá rxjs. Pro získání dat na server očekávám studený stream a tento stream používám v šabloně pomocí asyncPipe. Pokud tuto rouru použiji několikrát, pak se vrátím k definici studeného proudu, každá roura bude vyžadovat data ze serveru, což je přinejmenším podivné. A když převedu studený proud na teplý, tak požadavek jednou proběhne.

Obecně platí, že pochopení typu toků je pro začátečníky poměrně obtížné, ale důležité.

Operátoři

return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
    .pipe(
        tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
        map(({ data }: TradeCompanyList) => data)
    );

Operátoři nám poskytují možnost pracovat se streamy. Pomáhají kontrolovat události plynoucí v Observable. Zvážíme několik nejoblíbenějších a další informace o operátorech naleznete na odkazech v užitečných informacích.

Provozovatelé

Začněme pomocným operátorem. Vytváří Observable na základě jednoduché hodnoty.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Operátoři-filtr

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Operátor filtru, jak název napovídá, filtruje proudový signál. Pokud operátor vrátí hodnotu true, přeskočí dále.

Operátoři - vezměte

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

take - Bere hodnotu počtu emitů, po kterých stream končí.

Operators-debounceTime

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

debounceTime - zahodí emitované hodnoty, které spadají do zadaného časového intervalu mezi výstupními daty - po uplynutí časového intervalu vyšle poslední hodnotu.

const { Observable } = Rx;
const { debounceTime, take } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++)
  }, 1000);

 // Испускаем значение раз в 1500мс
  setInterval(() => {
    observer.next(i++)
  }, 1500);
}).pipe(
  debounceTime(700),  // Ожидаем 700мс значения прежде чем обработать
  take(3)
);  

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Operátoři-takeWhile

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Vysílá hodnoty, dokud takeWhile vrátí false a poté se odhlásí z vlákna.

const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  observer.next(i++);
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++)
  }, 1000);
}).pipe(
  takeWhile( producer =>  producer < 5 )
);  

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Operátoři-kombinovatNejnovější

Kombinovaný operátor combLatest je trochu podobný slibu.all. Kombinuje více proudů do jednoho. Poté, co každé vlákno provede alespoň jednu emisi, získáme nejnovější hodnoty z každého jako pole. Dále po každém emitování z kombinovaných toků poskytne nové hodnoty.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750мс
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

combineLatest(observer_1, observer_2).pipe(take(5));

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Operátory-zip

Zip - čeká na hodnotu z každého streamu a na základě těchto hodnot vytvoří pole. Pokud hodnota nepochází z žádného vlákna, skupina se nevytvoří.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

const { zip, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
});

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
});

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
});

zip(observer_1, observer_2, observer_3).pipe(take(5));

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Operátoři - forkJoin

forkJoin také spojuje vlákna, ale vyšle hodnotu pouze tehdy, když jsou všechna vlákna dokončena.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
}).pipe(take(3));

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
}).pipe(take(5));

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
}).pipe(take(4));

forkJoin(observer_1, observer_2, observer_3);

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Mapa operátorů

Operátor transformace mapy transformuje emitovanou hodnotu na novou.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

const {  Observable } = Rx;
const { take, map } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++);
  }, 1000);
}).pipe(
  map(x => x * 10),
  take(3)
);

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Operátoři – sdílejte, klepněte

Operátor tap vám umožňuje provádět vedlejší efekty, tedy jakékoli akce, které nemají vliv na sekvenci.

Provozovatel sdílené utility může proměnit studený proud na horký proud.

Asynchronní programování v JavaScriptu (Callback, Promise, RxJs)

Operátoři jsou hotoví. Pojďme k předmětu.

Přemýšlet nahlas

A pak jsem šel pít čaj. Už jsem z těch příkladů unavená 😀

Rodina předmětů

Skupina předmětů je ukázkovým příkladem horkých vláken. Tyto třídy jsou jakýmsi hybridem, který funguje jako pozorovatel a pozorovatel zároveň. Vzhledem k tomu, že předmět je žhavý stream, je nutné jej odhlásit. Pokud mluvíme o hlavních metodách, pak jsou to:

  • další - předání nových dat do streamu
  • chyba - chyba a ukončení vlákna
  • kompletní - konec vlákna
  • odběr - odběr streamu
  • unsubscribe - odhlásit se ze streamu
  • asObservable - transformace v pozorovatele
  • toPromise – promění se ve slib

Přidělte 4 5 typů předmětů.

Přemýšlet nahlas

Na streamu jsem řekl 4, ale ukázalo se, že přidali ještě jednu. Jak se říká, žij a uč se.

Jednoduchý předmět new Subject()- nejjednodušší druh předmětů. Vytvořeno bez parametrů. Předává hodnoty, které přišly až po předplatném.

Předmět chování new BehaviorSubject( defaultData<T> ) - dle mého názoru nejčastější typ předmětů. Vstup má výchozí hodnotu. Vždy ukládá data posledního čísla, která se přenáší při předplatném. Tato třída má také metodu užitečné hodnoty, která vrací aktuální hodnotu proudu.

Přehrát předmět new ReplaySubject(bufferSize?: number, windowTime?: number) - Volitelně může vzít jako první argument velikost vyrovnávací paměti hodnot, které v sobě uloží, a druhý čas, během kterého potřebujeme změny.

asynchronní předmět new AsyncSubject() - při přihlášení k odběru se nic neděje a hodnota bude vrácena až po dokončení. Bude vrácena pouze poslední hodnota streamu.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Dokumentace o tom mlčí a já sám to vidím poprvé. Kdo ví, co dělá, napište, doplníme.

Fuj. Dobře, zvážili jsme všechno, co jsem vám dnes chtěl říct. Doufám, že tyto informace byly užitečné. Seznam literatury si můžete přečíst sami v záložce Užitečné informace.

Užitečné informace

Zdroj: www.habr.com

Přidat komentář