Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Ahojte všetci. Sergey Omelnitsky je v kontakte. Nie je to tak dávno, čo som hostil stream o reaktívnom programovaní, kde som hovoril o asynchrónnosti v JavaScripte. Dnes by som si rád urobil poznámky k tomuto materiálu.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Ale skôr, než sa pustíme do hlavného materiálu, musíme urobiť úvodnú poznámku. Začnime teda definíciami: čo je zásobník a front?

Stoh je kolekcia, ktorej prvky sa získavajú na princípe LIFO typu last-in, first-out

otočenie je kolekcia, ktorej prvky sa získavajú na princípe FIFO prvý dovnútra, prvý von

Dobre, pokračujme.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

JavaScript je jednovláknový programovací jazyk. To znamená, že existuje iba jedno vlákno vykonávania a jeden zásobník, na ktorom sú funkcie zaradené do frontu na vykonanie. Preto môže JavaScript vykonávať iba jednu operáciu naraz, zatiaľ čo ostatné operácie počkajú na svoj rad v zásobníku, kým nebudú vyvolané.

Zásobník hovorov je dátová štruktúra, ktorá, zjednodušene povedané, zaznamenáva informácie o mieste v programe, kde sa nachádzame. Ak prejdeme do funkcie, posunieme jej vstup na vrchol zásobníka. Keď sa vrátime z funkcie, vyberieme najvyšší prvok zo zásobníka a skončíme tam, kde sme funkciu zavolali. Toto je všetko, čo zásobník dokáže. A teraz mimoriadne zaujímavá otázka. Ako potom funguje asynchrónnosť v JavaScripte?

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

V skutočnosti majú prehliadače okrem zásobníka aj špeciálny rad na prácu s takzvaným WebAPI. Funkcie v tomto fronte sa vykonajú v poradí až po úplnom vymazaní zásobníka. Až potom sa presunú z frontu do zásobníka na vykonanie. Ak je v zásobníku momentálne aspoň jeden prvok, nemožno ho pridať do zásobníka. Práve z tohto dôvodu nie je volanie funkcií podľa časového limitu často presné v čase, pretože funkcia sa nemôže dostať z frontu do zásobníka, kým je plný.

Pozrime sa na nasledujúci príklad a začnime s jeho implementáciou krok za krokom. Pozrime sa tiež, čo sa deje v systéme.

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

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

1) Zatiaľ sa nič nedeje. Konzola prehliadača je prázdna, zásobník hovorov je prázdny.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

2) Potom sa do zásobníka hovorov pridá príkaz console.log('Hi').

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

3) A je splnené

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

4) Potom sa console.log('Hi') odstráni zo zásobníka hovorov.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

5) Teraz prejdite na príkaz setTimeout(funkcia cb1() {… }). Pridá sa do zásobníka hovorov.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

6) Vykoná sa príkaz setTimeout(funkcia cb1() {… }). Prehliadač vytvorí časovač, ktorý je súčasťou webového rozhrania API. Vykoná odpočítavanie.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

7) Príkaz setTimeout(funkcia cb1() {...}) dokončil svoju prácu a je odstránený zo zásobníka hovorov.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

8) Príkaz console.log('Bye') sa pridá do zásobníka hovorov.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

9) Príkaz console.log('Bye') sa vykoná.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

10) Príkaz console.log('Bye') je odstránený zo zásobníka hovorov.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

11) Po uplynutí aspoň 5000 ms sa časovač ukončí a zaradí spätné volanie cb1 do frontu spätných volaní.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

12) Slučka udalostí prevezme funkciu cb1 z frontu spätných volaní a umiestni ju do zásobníka hovorov.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

13) Funkcia cb1 sa vykoná a pridá do zásobníka hovorov console.log('cb1').

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

14) Vykoná sa príkaz console.log('cb1').

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

15) Príkaz console.log('cb1') sa odstráni zo zásobníka hovorov.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

16) Funkcia cb1 je odstránená zo zásobníka hovorov.

Pozrime sa na príklad v dynamike:

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Pozreli sme sa na to, ako je asynchrónnosť implementovaná v JavaScripte. Teraz si povedzme stručne o vývoji asynchrónneho kódu.

Vývoj asynchrónneho 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);
                    })
                })
            })
        })
    })
});

Asynchrónne programovanie, ako ho poznáme v JavaScripte, je možné realizovať iba funkciami. Môžu byť odovzdané ako každá iná premenná iným funkciám. Takto sa zrodili spätné volania. A je to cool, zábavné a hravé, až kým sa to nezmení na smútok, melanchóliu a smútok. prečo? Je to jednoduché:

  • Ako sa zložitosť kódu zvyšuje, projekt sa rýchlo mení na nejasné, opakovane vnorené bloky - „peklo spätného volania“.
  • Spracovanie chýb sa dá ľahko prehliadnuť.
  • Nemôžete vrátiť výrazy s návratom.

S príchodom Promise sa situácia 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);
});

  • Objavili sa reťazce sľubov, ktoré zlepšili čitateľnosť kódu
  • Objavila sa samostatná metóda na zachytávanie chýb
  • Pridaná možnosť paralelného vykonávania pomocou Promise.all
  • Vnorenú asynchróniu môžeme vyriešiť pomocou async/await

Ale sľuby majú svoje obmedzenia. Napríklad sľub nemožno zrušiť bez tanca s tamburínou, a čo je najdôležitejšie, že funguje s jednou hodnotou.

No, hladko sme sa priblížili k reaktívnemu programovaniu. Unavený? No, našťastie, môžete si ísť uvariť čaj, popremýšľať a vrátiť sa a prečítať si viac. A budem pokračovať.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Reaktívne programovanie je programovacia paradigma zameraná na dátové toky a šírenie zmien. Pozrime sa bližšie na to, čo je to dátový tok.

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

const eventsArray = [];

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

Predstavme si, že máme vstupné pole. Vytvárame pole a pre každé zapísanie vstupnej udalosti uložíme udalosť do nášho poľa. Zároveň by som rád poznamenal, že naše pole je zoradené podľa času, t.j. index neskorších udalostí je väčší ako index predchádzajúcich udalostí. Takéto pole je zjednodušeným modelom toku údajov, ale ešte to nie je tok. Aby sa toto pole mohlo bezpečne nazývať stream, musí byť schopné nejakým spôsobom informovať predplatiteľov, že do neho prišli nové dáta. Tak sa dostávame k definícii toku.

Dátový tok

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

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

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

potok je pole údajov zoradených podľa času, ktoré môžu naznačovať, že sa údaje zmenili. Teraz si predstavte, aké pohodlné je napísať kód, v ktorom jedna akcia vyžaduje volanie niekoľkých udalostí v rôznych častiach kódu. Jednoducho sa prihlásite na odber streamu a on nás upozorní, keď nastanú zmeny. A knižnica RxJs to dokáže.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

RxJS je knižnica pre prácu s asynchrónnymi a udalosťami založenými programami pomocou pozorovateľných sekvencií. Knižnica poskytuje základný typ Pozorovateľné, niekoľko pomocných typov (Pozorovateľ, plánovači, subjekty) a operátorov pre prácu s udalosťami ako s kolekciami (mapovať, filtrovať, znižovať, každý a podobné z poľa JavaScript).

Poďme pochopiť základné pojmy tejto knižnice.

Pozorovateľný, pozorovateľ, producent

Pozorovateľný je prvý základný typ, na ktorý sa pozrieme. Táto trieda obsahuje hlavnú časť implementácie RxJs. Je spojený s pozorovateľným tokom, ktorý je možné odoberať pomocou metódy odberu.

Observable implementuje pomocný mechanizmus na vytváranie aktualizácií, tzv hodinky. Zdroj hodnôt pre Pozorovateľa sa nazýva Výrobca. Môže to byť pole, iterátor, webový soket, nejaký druh udalosti atď. Môžeme teda povedať, že pozorovateľné je vodič medzi producentom a pozorovateľom.

Pozorovateľné spracováva tri typy udalostí pozorovateľa:

  • ďalšie – nové údaje
  • chyba – chyba, ak sa sekvencia skončila v dôsledku výnimky. táto udalosť tiež znamená dokončenie sekvencie.
  • kompletný — signál o dokončení sekvencie. To znamená, že už nebudú žiadne nové údaje.

Pozrime sa na demo:

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Na začiatku spracujeme hodnoty 1, 2, 3 a po 1 sekunde. dostaneme 4 a ukončíme náš stream.

Myslieť nahlas

A potom som si uvedomil, že rozprávať o tom je zaujímavejšie ako o tom písať. 😀

Predplatné

Keď sa prihlásite na odber streamu, vytvoríme novú triedu predplatnéčo nám dáva možnosť odhlásiť sa pomocou metódy odhlásiť. Pomocou metódy môžeme tiež zoskupovať odbery pridať. Je logické, že môžeme rozdeliť vlákna pomocou odstrániť. Metódy pridania a odstránenia akceptujú ako vstup ďalšie predplatné. Chcel by som poznamenať, že keď sa odhlásime, odhlásime všetky detské odbery, ako keby zavolali metódu odhlásenia. Pokračuj.

Typy prúdov

HOT
CHLADNÝ

Producent je vytvorený mimo pozorovateľné
Producent je vytvorený vo vnútri pozorovateľného

Údaje sa prenášajú v čase vytvorenia pozorovateľného prvku
Údaje sa poskytujú v čase predplatného

Na zrušenie odberu potrebujete ďalšiu logiku
Vlákno sa ukončí samo

Používa vzťah jeden k mnohým
Používa vzťah typu one-to-one

Všetky predplatné majú rovnaký význam
Predplatné sú nezávislé

Ak nemáte predplatné, môžete prísť o údaje
Znova vydá všetky hodnoty streamu pre nové predplatné

Aby som dal analógiu, myslel by som si, že horúci stream je film v kine. V akom časovom bode ste prišli, od tej chvíle ste sa začali pozerať. Studený tok by som prirovnal k hovoru v technike. podpora. Každý volajúci si vypočuje záznam hlasovej schránky od začiatku do konca, ale môžete zavesiť pomocou zrušenia odberu.

Chcel by som poznamenať, že existujú aj takzvané teplé prúdy (s touto definíciou som sa stretol veľmi zriedkavo a len v cudzích komunitách) - ide o prúdenie, ktoré sa transformuje zo studeného prúdenia na horúce. Vyvstáva otázka - kde použiť)) Uvediem príklad z praxe.

Pracujem s Angularom. Aktívne používa rxjs. Na prijímanie údajov na server očakávam studené vlákno a použijem toto vlákno v šablóne pomocou asyncPipe. Ak použijem túto trubicu niekoľkokrát, potom, keď sa vrátim k definícii studeného prúdu, každá trubica bude vyžadovať údaje zo servera, čo je prinajmenšom zvláštne. A ak premením studený prúd na teplý, tak sa žiadosť raz stane.

Vo všeobecnosti je pochopenie typu tokov pre začiatočníkov dosť ťažké, ale dôležité.

operátori

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

Operátori nám poskytujú možnosť rozšíriť našu schopnosť pracovať s prúdmi. Pomáhajú kontrolovať udalosti vyskytujúce sa v Pozorovateľnom. Pozrieme sa na pár najpopulárnejších a ďalšie podrobnosti o operátoroch nájdete pomocou odkazov v užitočných informáciách.

Operátori - z

Začnime pomocným operátorom. Vytvára pozorovateľné na základe jednoduchej hodnoty.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Operátory - filter

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Operátor filtra, ako už názov napovedá, filtruje prúdový signál. Ak operátor vráti true, preskočí ďalej.

Operátori – zober

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

take — Berie hodnotu počtu emitorov, po ktorých vlákno končí.

Operátory - debounceTime

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

debounceTime - zahodí emitované hodnoty, ktoré spadajú do zadaného časového intervalu medzi výstupmi - 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)
);  

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Operátori - takeWhile

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Vysiela hodnoty, kým takeWhile vráti hodnotu false, po ktorej sa odhlási 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 )
);  

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Operátory - kombajnNajnovšie

Operátor CombiLatest je do istej miery podobný operátorovi sľub.all. Spája viacero vlákien do jedného. Keď každé vlákno vykoná aspoň jednu emisiu, získame najnovšie hodnoty z každého vo forme poľa. Ďalej, po akejkoľvek emisii zo zlúčených tokov poskytne nové hodnoty.

Asynchrónne programovanie v JavaScripte (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));

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Operátory - zips

Zip – čaká na hodnotu z každého vlákna a na základe týchto hodnôt vytvorí pole. Ak hodnota nepochádza zo žiadneho vlákna, skupina sa nevytvorí.

Asynchrónne programovanie v JavaScripte (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));

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Operátory - forkJoin

forkJoin tiež spája vlákna, ale vygeneruje hodnotu iba vtedy, keď sú všetky vlákna dokončené.

Asynchrónne programovanie v JavaScripte (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);

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Operátori - mapa

Operátor transformácie mapy transformuje hodnotu emitora na novú.

Asynchrónne programovanie v JavaScripte (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)
);

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

Operátori – zdieľajte, klepnite

Operátor kohútika vám umožňuje vykonávať vedľajšie účinky, to znamená akékoľvek akcie, ktoré neovplyvňujú postupnosť.

Prevádzkovateľ zdieľanej siete môže premeniť studený prúd na horúci.

Asynchrónne programovanie v JavaScripte (Callback, Promise, RxJs)

S operátormi sme skončili. Prejdime na Predmet.

Myslieť nahlas

A potom som si išiel vypiť čaj. Som unavená z týchto príkladov 😀

Rodina predmetov

Skupina predmetov je ukážkovým príkladom horúcich tokov. Tieto triedy sú akýmsi hybridom, ktorý pôsobí súčasne ako pozorovateľ a pozorovateľ. Keďže téma je horúcou niťou, je potrebné sa z nej odhlásiť. Ak hovoríme o hlavných metódach, potom sú to:

  • ďalšie – prenos nových dát do streamu
  • chyba – chyba a ukončenie vlákna
  • dokončenie – dokončenie vlákna
  • prihlásiť sa – prihlásiť sa na odber streamu
  • unsubscribe – odhlásenie zo streamu
  • asObservable – premeniť sa na pozorovateľa
  • toPromise – premení sa na sľub

Existuje 4 5 druhov predmetov.

Myslieť nahlas

Na streame sa rozprávali 4 ľudia, no ukázalo sa, že pridali ešte jedného. Ako sa hovorí, ži a uč sa.

Jednoduchý predmet new Subject()– najjednoduchší typ predmetov. Vytvorené bez parametrov. Prenáša hodnoty prijaté až po predplatení.

Predmet správania new BehaviorSubject( defaultData<T> ) – podľa mňa najbežnejší typ predmetu. Vstup má predvolenú hodnotu. Vždy ukladá údaje posledného čísla, ktoré sa prenáša pri prihlásení. Táto trieda má tiež metódu užitočnej hodnoty, ktorá vracia aktuálnu hodnotu prúdu.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) — Vstup môže voliteľne brať ako prvý argument veľkosť vyrovnávacej pamäte hodnôt, ktoré v sebe uloží, a ako druhý čas, počas ktorého potrebujeme zmeny.

AsyncSubject new AsyncSubject() — pri prihlásení na odber sa nič nestane a hodnota sa vráti až po dokončení. Vráti sa iba posledná hodnota streamu.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — Dokumentácia o ňom mlčí a ja ho vidím prvýkrát. Ak niekto vie, čo robí, napíšte a my to doplníme.

Fuj. Nuž, prebrali sme všetko, čo som vám dnes chcel povedať. Dúfam, že tieto informácie boli užitočné. Zoznam referencií si môžete prečítať sami v záložke užitočné informácie.

užitočné informácie

Zdroj: hab.com

Pridať komentár