Vsem pozdrav! Tukaj je Sergej Omelnitski. Nedavno sem imel prenos v živo o reaktivnem programiranju, kjer sem govoril o asinhronosti v JavaScriptu. Danes bi rad strnil to gradivo.

Preden začnemo s glavnim gradivom, potrebujemo uvod. Torej, začnimo z definicijami: kaj je sklad in kaj je vrsta?
Sklad je zbirka, katere elementi se obravnavajo po načelu 'zadnji vstopil, prvi izstopil' LIFO.
Vrsta je zbirka, katere elementi se obravnavajo po načelu 'prvi vstopil, prvi izstopil' FIFO.
Okej, nadaljujmo.

JavaScript je enovreten programski jezik. To pomeni, da obstaja le en tok izvajanja in en sklad, v katerega se funkcije dodajajo v vrsto za izvajanje. Zato lahko JavaScript v enem trenutku izvede le eno operacijo, druge operacije pa čakajo na vrsto v skladu, dokler niso poklicane.
Sklad klicev je podatkovna struktura, ki, poenostavljeno povedano, zabeleži informacije o tem, kje smo v programu. Ko preidemo v funkcijo, v zgornji del sklada dodamo zapis o njej. Ko se iz funkcije vrnemo, vzamemo iz sklada najvišji element in se vrnemo tja, od koder smo klicali to funkcijo. To je vse, kar sklad zmore. In zdaj se pojavi zelo zanimivo vprašanje. Kako torej deluje asinhronost v JavaScriptu?

Dejansko poleg sklada v brskalnikih obstaja posebna vrsta za delo s tako imenovanim WebAPI. Funkcije iz te vrste se izvršijo po vrstnem redu šele po tem, ko je sklad popolnoma prazen. Šele potem so premaknjene iz vrste v sklad za izvajanje. Če je v skladu trenutno vsaj en element, potem ne morejo priti v sklad. Prav zato so klici funkcij z zamudo pogosto časovno netočni, ker funkcija ne more priti iz vrste v sklad, dokler je ta poln.
Poglejmo naslednji primer in se ukvarjajmo z njegovim 'izvajanjem' korak za korakom. Poleg tega bomo pogledali, kaj se pri tem dogaja v sistemu.
console.log('Hi);
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
1) Na začetku se nič ne dogaja. Konzola brskalnika je prazna, sklad klicev je prazen.

2) Potem se ukaz console.log(‘Hi’) doda v sklad klicev.

3) In se izvrši.

4) Potem se console.log(‘Hi’) odstrani iz sklada klicev.

5) Zdaj gremo na ukaz setTimeout(function cb1() {… }). Ta se doda v sklad klicev.

6) Ukaz setTimeout(function cb1() {… }) se izvrši. Brskalnik ustvari časovnik, ki je del Web API. Ta bo izvajal odštevanje časa.

7) Ukaz setTimeout(function cb1() {… }) konča svoje delo in se odstrani iz sklada klicev.

8) Ukaz console.log(‘Bye’) se doda v sklad klicev.

9) Ukaz console.log(‘Bye’) se izvrši.

10) Ukaz console.log(‘Bye’) se odstrani iz sklada klicev.

11) Ko mine vsaj 5000 ms, časovnik konča svoje delo in doda povratni klic cb1 v vrsto povratnih klicev.

12) Dogodkovna zanka vzame funkcijo cb1 iz vrste povratnih klicev in jo doda v sklad klicev.

13) Funkcija cb1 se izvrši in doda console.log(‘cb1’) v sklad klicev.

14) Ukaz console.log(‘cb1’) se izvrši.

15) Ukaz console.log(‘cb1’) se odstrani iz sklada klicev.

16) Funkcija cb1 se odstrani iz sklada klicev.
Poglejmo primer v dinamiki:

Tako smo obravnavali, kako je asynchronicity izvedena v JavaScriptu. Zdaj pa hitro preidimo na evolucijo asinhronega kode.
Evolucija asinhronega kode.
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);
})
})
})
})
})
});Asinhrono programiranje, kakršnega poznamo v JavaScriptu, lahko izvedemo samo z funkcijami. Te lahko predamo drugim funkcijam, kot katerokoli drugo spremenljivko. Tako so nastali povratni klici. In to je zabavno, dokler se ne spremeni v žalost, obup in žalost. Zakaj? Preprosto:
- S povečevanjem zapletenosti kode se projekt hitro spremeni v malo razumljive večkratno utelešene bloke — "callback hell".
- Obravnavo napak je enostavno spregledati.
- Izrazov s return ni mogoče vračati.
S pojavom Promise se je stanje nekoliko izboljšalo.
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);
});- Pojavili so se verigi obljub, kar je izboljšalo berljivost kode.
- Pojavil se je poseben način za zajemanje napak.
- Pojavila se mogućnost paralelnog izvršavanja uz pomoć Promise.all
- Ugniježdenu asinhronost možemo rešiti pomoću async/await
Ali obećanje ima svoja ograničenja. Na primer, obećanje se ne može otkazati bez dodatnih trikova, a najvažnije — radi sa jednom vrednošću.
Pa, polako smo došli do reaktivnog programiranja. Umorni ste? Pa dobro, možete da skuvate čaj, malo razmislite i vratite se da čitate dalje. A ja ću nastaviti.

Reaktivno programiranje— paradigma programiranja usmerena na tokove podataka i širenje promena. Hajde da detaljnije razjasnimo šta je to tok podataka.
// Получаем ссылку на элемент
const input = ducument.querySelector('input');
const eventsArray = [];
// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);Zamislimo da imamo polje za unos. Kreiramo niz, i na svaki keyup događaj input-a ćemo čuvati događaj u našem nizu. Pri tome, želeo bih da napomenem da je naš niz sortiran po vremenu — tj. indeks kasnijih događaja je veći od indeksa ranijih. Takav niz predstavlja pojednostavljeni model toka podataka, ali to još nije tok. Da bi se ovaj niz mogao smatrati tokom, on mora nekako obavestiti pretplatnike da su mu novi podaci dostupni. Tako smo došli do definicije toka.
Tok podataka
const { interval1 } = Rx;
const { take } = RxOperators;
interval(1000).pipe(
take(4)
)
Tok— to je niz podataka, sortiranih po vremenu, koji može obavestiti da su se podaci promenili. A sada zamislite koliko je zgodno pisati kod u kojem će jedno dejstvo zahtevati nekoliko događaja na različitim mestima u kodu. Jednostavno se pretplatimo na tok i on će nam sam javiti kada dođe do promena. I to može da uradi biblioteka RxJs.

RxJS — to je biblioteka za rad sa asinhronim i događajima zasnovanim programima koristeći posmatrane nizove. Biblioteka pruža osnovni tip Observable, nekoliko pomoćnih tipova (Observer, Schedulers, Subjects) i operatore za rad sa događajima kao kolekcijama (map, filter, reduce, every i slične iz JavaScript Array).
Hajde da se upoznamo sa osnovnim pojmovima ove biblioteke.
Observable, Observer, Producer
Observable — prvi osnovni tip koji ćemo razmotriti. Ova klasa sadrži većinu implementacije RxJs. Povezana je sa posmatranim tokom na koji se može pretplatiti koristeći metodu subscribe.
U Observable se implementira pomoćni mehanizam za kreiranje ažuriranja, koji se naziva Observer. Izvor vrednosti za Observer naziva se Producer. To može biti niz, iterator, web socket, neki događaj itd. Tako da možemo reći da je observable posrednik između Producer i Observer.
Observable obrađuje tri vrste događaja Observer:
- next – novi podaci
- error – greška, ako je sekvenca okončana zbog izuzetne situacije. Ovaj događaj takođe podrazumeva završetak sekvence.
- complete — signal o završetku sekvence. To znači da novih podataka više neće biti.
Pogledajmo demo:

Na početku ćemo obraditi vrednosti 1, 2, 3, a nakon 1 sekunde dobićemo 4 i završiti naš tok.
Misli naglas
I tada sam shvatio da je pričanje bilo zanimljivije od pisanja o tome. 😀
Subscription
Kada se pretplatimo na tok, kreiramo novu klasu subscription, koja nam omogućava da otkažemo pretplatu koristeći metodu unsubscribe. Takođe možemo grupisati pretplate koristeći metodu add. Logično je da možemo razgrupisati tokove koristeći remove. Metodi add i remove uzimaju drugu pretplatu kao ulaz. Vredno je napomenuti da kada otkažemo pretplatu, otkazujemo se od svih podređenih pretplata kao da smo i njima pozvali metodu unsubscribe. Idemo dalje.
Vrste tokova
| HOT | COLD |
|---|---|
| Producer se kreira spolja observable-a | Producer se kreira unutar observable-a |
| Podaci se prenose u trenutku kreiranja observable-a | Podaci se dostavljaju u trenutku pretplate |
| Potrebna je dodatna logika za otkazivanje pretplate | Tok se završava samostalno |
| Koristi vezu jedan-ka-više | Koristi vezu jedan-na-jedan |
| Sve pretplate imaju jedinstvenu vrednost | Pretplate su nezavisne |
| Podaci se mogu izgubiti ako nema pretplate | Ponovo objavljuje sve vrednosti toka za novu pretplatu |
Če potegnemo analogijo, bi vroč tok predstavil kot film v kinu. Od trenutka, ko si prišel, začneš ogled. Hladen tok bi primerjal z klicem v tehnično podporo. Vsak, ki pokliče, posluša posnetek avtomatskega odgovora od začetka do konca, vendar lahko prekine klic s pomočjo unsubscribe.
Rad bi opozoril, da obstajajo tudi tako imenovani topli tokovi (to definicijo sem srečal zelo redko in le v tujih skupnostih) — to je tok, ki se transformira iz hladnega v vročega. Pojavi se vprašanje — kje ga uporabiti)) Prikazal bom primer iz prakse.
Delam z Angularem. Aktivno uporablja rxjs. Za pridobivanje podatkov na strežniku pričakujem hladen tok in ta tok uporabljam v predlogi s pomočjo asyncPipe. Če uporabljam to cev večkrat, se vrnem k definiciji hladnega toka, vsaka cev bo zahtevala podatke s strežnika, kar je milo rečeno nenavadno. Če pa spremenim hladen tok v topli, bo zahteva izvedena enkrat.
Dejansko je razumevanje vrst tokov precej zapleteno za začetnike, vendar je pomembno.
Operaterji
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);Možnosti za delo s tokovi nam ponujajo operaterji. Ti pomagajo nadzorovati dogodke, ki se odvijajo v Observable. Oglejali si bomo nekaj najbolj priljubljenih, podrobneje pa se lahko seznanite z operaterji prek povezav v koristnih informacijah.
Operaterji — of
Začnimo s pomožnim operaterjem of. Ustvari Observable na osnovi preproste vrednosti.

Operaterji — filter

Operater filtriranja filter, kot lahko razumemo iz imena, filtrira signal toka. Če operater vrne resnico, ga naprej spusti.
Operaterji — take

take — sprejme število emitov, po katerem zaključi tok.
Operaterji — debounceTime

debounceTime — zavrže izpuščene vrednosti, ki pridejo v določenem časovnem okviru med izhodnimi podatki — po preteku časovnega intervala izpusti zadnjo vrednost.
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)
); 
Операторы — takeWhile

Излучает значения, пока takeWhile не вернет false, после чего отписывается от потока.
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 )
); 
Операторы — combineLatest
Комбинированный оператор combineLatest похож на promise.all. Он объединяет несколько потоков в один. Как только каждый поток выполнит хотя бы одно излучение, мы получаем последние значения от каждого в виде массива. После любого нового излучения из объединённых потоков он будет возвращать новые значения.

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));
Операторы — zip
Zip — ждёт значение из каждого потока и формирует массив на основе этих значений. Если значение не поступит из какого-либо потока, то группа не будет сформирована.

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));
Операторы — forkJoin
forkJoin также объединяет потоки, но он излучает значение только когда все потоки завершены (complete).

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emitting value every 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
}).pipe(take(3));
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emitting value every 750
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
}).pipe(take(5));
const observer_3 = Observable.create((observer) => {
let i = 1;
// Emitting value every 500
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
}).pipe(take(4));
forkJoin(observer_1, observer_2, observer_3);
Operatorji — map
Operator transformacije map pretvara emitovanu vrednost u novu.

const { Observable } = Rx;
const { take, map } = RxOperators;
Observable.create((observer) => {
let i = 1;
// Emitting value every 1000ms
setInterval(() => {
observer.next(i++);
}, 1000);
}).pipe(
map(x => x * 10),
take(3)
);

Operatorji – share, tap
Operator tap omogućava izvođenje sporednih efekata, tj. bilo kakve radnje koje ne utiču na sekvencu.
Utility operator share može pretvoriti hladan tok u vruć.

Sa operatorima smo završili. Prelazimo na Subject.
Misli naglas
I onda sam otišao da pijem čaj. Umorili su me ovi primeri 😀
Familija subject-ova
Familija subject-ova je živopisan primer vrućih tokova. Ove klase su neka vrsta hibrida, koje deluju istovremeno kao observable i observer. Pošto je subject vrući tok, potrebno je odjaviti se od njega. Ako govorimo o osnovnim metodama, to su:
- next – prenos novih podataka u tok
- error – greška i završetak toka
- complete – završetak toka
- subscribe – pretplatiti se na tok
- unsubscribe – odjaviti se od toka
- asObservable – transformišemo u posmatrača
- toPromise – transformiše u promise
Izdvajamo 4 do 5 tipova subject-ova.
Misli naglas
Na streamu sam govorio o 4, a ispostavilo se da su dodali još jedan. Kao što kažu, vek živi vek uči.
Običan Subject new Subject()– najjednostavnija vrsta subject-ova. Kreira se bez parametara. Prenosi vrednosti koje su stigle samo nakon pretplate.
BehaviorSubject new BehaviorSubject( defaultData ) – po mom mišljenju, najraširenija vrsta subject-ova. Prima vrednost po default-u. Uvek čuva podatke poslednjeg emita, koje prenosi pri pretplati. Ova klasa takođe ima korisnu metodu value, koja vraća trenutnu vrednost toka.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) — Na ulazu može opcionalno primiti prvi argument kao veličinu baferirajuće vrednosti koju će čuvati, a drugi kao vreme tokom kojeg su nam potrebne izmenjene vrednosti.
AsyncSubject novi AsyncSubject() — pri naročanju se nič ne zgodi, vrednost pa bo vrnjena le ob complete. Vrnjena bo le zadnja vrednost toka.
WebSocketSubject nov WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — O njem dokumentacija molči in ga prvič vidim. Kdor ve, kaj počne, naj piše, dopolnimo.
Uff. No, pregledali smo vse, kar sem želel danes povedati. Upam, da so bile te informacije koristne. Seznam literature si lahko ogledate v zavihku uporabne informacije.
Uporabne informacije
- — povezava na stream
- — Kako deluje JS: pregled motorja, mehanizmov izvajanja, sklada klicev
- — Kako deluje JS: cikel dogodkov, asinhronost in pet načinov za izboljšanje kode s pomočjo async / await
- — Kako deluje Event Loop v JavaScriptu
- — Evolucija asinhronega JavaScript
- — Kaj je RxJS in zakaj je koristno vedeti zanj
- — Praktična uporaba RxJS
- — RxJS Observables Tutorial — Ustvarjanje in naročanje na Observables
- — RXJS: Vroče in hladne observables
- — Razredi in funkcije za ustvarjanje Observable. Operatorji.
- — RxJS Operatorji po exempelih
- — Seznam API-jev
- — Vrste Subject in Razporedi v RxJS
Izvor: habr.com
