Pozdrav svima. Ovde je Sergej Omelnitzki. Nedavno sam imao stream o reaktivnom programiranju, gde sam pričao o asinhronosti u JavaScriptu. Danas bih želeo da sažmem ovaj materijal.

Ali pre nego što započnemo glavni materijal, potrebno je da uradimo uvod. Dakle, hajde da počnemo s definicijama: šta je stek i red?
Stek je kolekcija čiji se elementi uzimaju po principu „poslednji došao, prvi otišao“ LIFO
Red je kolekcija čiji se elementi uzimaju po principu „prvi došao, prvi otišao“ FIFO
U redu, nastavljamo.

JavaScript je jezgro za programiranje sa jednim tokom. To znači da postoji samo jedan tok izvršavanja i jedan stek, u koji se funkcije stavljaju u red za izvršavanje. Samim tim, u jednom trenutku JavaScript može izvršiti samo jednu operaciju, dok ostale operacije čekaju svoj red u steku, dok ne budu pozvane.
Stek poziva je struktura podataka koja, pojednostavljeno rečeno, beleži informacije o mestu u programu gde se nalazimo. Ako ulazimo u funkciju, smeštamo njen zapis na vrh steka. Kada se vraćamo iz funkcije, uzimamo najgornji element iz steka i vraćamo se na mesto odakle smo pozvali tu funkciju. To je sve što stek radi. A sada izuzetno zanimljivo pitanje. Kako onda radi asinhronost u JavaScriptu?

U stvari, pored steka, u browserima postoji poseban red za rad sa takozvanim WebAPI. Funkcije iz ovog reda biće izvršene redom tek nakon što se stek potpuno isprazni. Tek tada se prebacuju iz reda u stek za izvršavanje. Ako u steku trenutno postoji barem jedan element, one ne mogu doći u stek. Zbog toga, pozivi funkcija putem timeouta često nisu tačni u vremenu, jer funkcija ne može ući iz reda u stek dok je on pun.
Razmotrićemo sledeći primer i posvetiti se njegovom korak-po-korak „izvršenju“. Takođe, razmotrićemo šta se u tom trenutku dešava u sistemu.
console.log('Hi);
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
1) Dok se ništa ne dešava. Konzola browsera je prazna, stek poziva je prazan.

2) Potom se komanda console.log('Hi') dodaje u stek poziva.

3) I ona se izvršava

4) Zatim se console.log(‘Hi’) uklanja iz stoga poziva.

5) Sada prelazimo na naredbu setTimeout(function cb1() {… }). Ona se dodaje u stog poziva.

6) Naredba setTimeout(function cb1() {… }) se izvršava. Preglednik kreira tajmer koji je deo Web API-ja. On će izvršiti odbrojavanje.

7) Naredba setTimeout(function cb1() {… }) je završila sa radom i uklanja se iz stoga poziva.

8) Naredba console.log(‘Bye’) se dodaje u stog poziva.

9) Naredba console.log(‘Bye’) se izvršava.

10) Naredba console.log(‘Bye’) se uklanja iz stoga poziva.

11) Nakon što prođe najmanje 5000 ms, tajmer završava sa radom i stavlja povratni poziv cb1 u red povratnih poziva.

12) Ciklus događaja uzima funkciju cb1 iz reda povratnih poziva i stavlja je u stog poziva.

13) Funkcija cb1 se izvršava i dodaje console.log(‘cb1’) u stog poziva.

14) Naredba console.log(‘cb1’) se izvršava.

15) Naredba console.log(‘cb1’) se uklanja iz stoga poziva.

16) Funkcija cb1 se uklanja iz stoga poziva.
Pogledajmo primer u dinamici:

Evo kako smo obradili asinkronost u JavaScript-u. Sada hajde da ukratko razgovaramo o evoluciji asinkronog koda.
Evolucija asinkronog koda.
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);
})
})
})
})
})
});Asinkrono programiranje, kakvo ga poznajemo u JavaScript-u, može se realizovati samo funkcijama. One se mogu preneti kao bilo koja druga promenljiva drugim funkcijama. Tako su rođeni povratni pozivi. I to je zabavno i veselo, sve dok se ne pretvori u tugu i jad. Zašto? Zato što:
- Sa povećanjem složenosti koda, projekat brzo postaje teško razumljiv sa više nivoa ugnježdenih blokova — „ozbiljna tjeskoba sa povratnim pozivima“.
- Obrada grešaka se lako može propustiti.
- Ne možete vraćati izraze sa return.
Sa pojavom Promise situacija se malo poboljšala.
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 2000);
}).then((result) => {
alert(result);
return result + 2;
}).then((result) => {
throw new Error('NEUSPJEH OVDJE');
alert(result);
return result + 2;
}).then((result) => {
alert(result);
return result + 2;
}).catch((e) => {
console.log('greška: ', e);
});- Pojavili su se lanci proma, što je poboljšalo čitljivost koda
- Pojavila se posebna metoda za hvatanje grešaka
- Pojavila se mogućnost paralelnog izvršavanja pomoću Promise.all
- Ugniježđenu asinkronost možemo riješiti pomoću async/await
Ali promisi imaju svoja ograničenja. Na primjer, niti jedan promisor se ne može otkazati bez dodatnog truda, a što je još važnije — radi sa jednom vrijednošću.
Pa, polako smo došli do reaktivnog programiranja. Umorni ste? Pa dobro, možete skuhati čaj, razmisliti i vratiti se čitanju. A ja ću nastaviti.

Reaktivno programiranje— paradigma programiranja usmjerena na tokove podataka i distribuciju promjena. Pogledajmo detaljnije što 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 unosa ćemo spremiti događaj u našem nizu. Također, važno je napomenuti da je naš niz sortirano prema 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 bismo ovaj niz mogli slobodno nazvati tokom, mora na neki način obavijestiti pretplatnike da su stigli novi podaci. Na taj način 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 prema vremenu, koji može obavijestiti da su podaci promijenjeni. A sada zamislite koliko je zgodno pisati kod u kojem će na jedan događaj biti potrebno pozvati nekoliko događaja na različitim mjestima u kodu. Jednostavno se pretplatimo na tok i on će nas sam obavijestiti kada dođe do promjena. To može učiniti biblioteka RxJs.

RxJS — to je biblioteka za rad s asinkronim i događajima temeljenim programima koristeći promatračke sekvence. Biblioteka pruža osnovni tip Observable, nekoliko pomoćnih tipova (Observer, Schedulers, Subjects) i operatore za rad s događajima kao kolekcijama (map, filter, reduce, every i slične iz JavaScript Array).
Pogledajmo osnovne pojmove ove biblioteke.
Observable, Observer, Producer
Observable — prvi osnovni tip koji ćemo razmotriti. Ova klasa sadrži glavni deo implementacije RxJs. Ona je povezana s posmatranim tokom kojem se može pristupiti putem metode subscribe.
U Observable se implementira pomoćni mehanizam za kreiranje ažuriranja, takozvani Observer. Izvor vrednosti za Observer se naziva Producer. To može biti niz, iterator, web soket, 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šku, ako se sekvenca završila 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 dobijamo 4 i završavamo naš tok.
Misli na glas
I tu sam shvatio da je zanimljivije pričati nego pisati o tome. 😀
Subscription
Kada se pretplatimo na tok, kreiramo novu klasu subscription, koja nam omogućava da otkažemo pretplatu putem metode unsubscribe. Takođe možemo grupisati pretplate pomoću metode add. Pa je logično da možemo odgrupisati tokove pomoću remove. Metode add i remove prihvataju drugu pretplatu kao argument. Voleo bih naglasiti da kada se odjavimo, odjavljujemo se od svih podpretplata kao da smo i njima pozvali metodu unsubscribe. Idemo dalje.
Vrste tokova
| HOT | COLD |
|---|---|
| Producer se kreira spolja observable | Producer se kreira unutar observable |
| Podaci se prenose prilikom kreiranja observable | Podaci se dostavljaju prilikom pretplate |
| Potrebna je dodatna logika za odjavu | Tok se završava samostalno |
| Koristi vezu jedan-na-mnoge | Koristi vezu jedan-na-jedan |
| Sve pretplate imaju jedinstvenu vrednost | Pretplate su nezavisne |
| Podaci se mogu izgubiti ako nema pretplate | Ponovno izdaje sve vrednosti toka za novu pretplatu |
Ako bismo povukli analogiju, predstaviću vrući tok kao film u bioskopu. U kom trenutku si došao, od tog trenutka i počinješ gledanje. Hladni tok bih uporedio sa pozivom u tehničku podršku. Svaki pozivatelj sluša snimak automatskog odgovara od početka do kraja, ali možeš prekinuti vezu putem unsubscribe.
Želio bih napomenuti da postoje takozvani topli tokovi (ovakvo se određivanje rijetko susreće, i to samo u stranim zajednicama) — to je tok koji se transformira iz hladnog u vrući. Postavlja se pitanje — gdje ga koristiti? Pružit ću primjer iz prakse.
Radim s Angularom. Aktivno koristi RxJS. Za dobivanje podataka s poslužitelja očekujem hladni tok i taj tok koristim u predlošku uz pomoć asyncPipe. Ako koristim ovu cijev nekoliko puta, vraćajući se na definiciju hladnog toka, svaka cijev će zahtijevati podatke s poslužitelja, što je blago rečeno čudno. A ako transformiram hladni tok u topli, upit će se izvršiti samo jednom.
Uopće, razumijevanje vrsta tokova prilično je složeno za početnike, ali važno je.
Operatori
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);Moguće je proširiti mogućnosti rada s tokovima putem operatora. Oni pomažu u kontroliranju događaja koji se odvijaju u Observable-u. Razmotrit ćemo nekoliko najpopularnijih, a detaljnije informacije o operatorima možete pronaći na vezama u korisnim informacijama.
Operatori — of
Počnimo s pomoćnim operatorom of. On stvara Observable na temelju jednostavne vrijednosti.

Operatori — filter

Operator filtriranja filter, kao što se može naslutiti iz naziva, filtrira signal toka. Ako operator vraća istinu, propušta dalje.
Operatori — take

take — Prima vrijednost broja emitiranih, nakon čega završava tok.
Operatori — debounceTime

debounceTime — odbacuje emitirane vrijednosti koje ulaze u određeni vremenski interval između izlaznih podataka — nakon proteka tog vremenskog intervala emitira posljednju vrijednost.
const { Observable } = Rx;
const { debounceTime, take } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Emituje vrijednost svake 1000ms
setInterval(() => {
observer.next(i++)
}, 1000);
// Emituje vrijednost svake 1500ms
setInterval(() => {
observer.next(i++)
}, 1500);
}).pipe(
debounceTime(700), // Čekamo 700ms prije obrade vrijednosti
take(3)
); 
Operatori — takeWhile

Emitira vrijednosti sve dok takeWhile ne vrati false, nakon čega se odjavljuje od toka.
const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Emitira vrijednost svakih 1000ms
setInterval(() => {
observer.next(i++)
}, 1000);
}).pipe(
takeWhile(producer => producer < 5)
); 
Operatori — combineLatest
Kombinirani operator combineLatest je nalik promise.all. Spaja nekoliko tokova u jedan. Nakon što svaki tok izvrši barem jednu emitaciju, dobijamo posljednje vrijednosti iz svakog u obliku niza. Zatim, nakon bilo koje emitacije iz spojenih tokova, vratit će nove vrijednosti.

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emitira vrijednost svakih 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emitira vrijednost svakih 750ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
combineLatest(observer_1, observer_2).pipe(take(5));
Operatori — zip
Zip čeka vrijednost iz svakog toka i formira niz na temelju tih vrijednosti. Ako vrijednost ne dođe iz nekog toka, grupa neće biti formirana.

const { zip, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emitira vrijednost svakih 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emitira vrijednost svakih 750
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
const observer_3 = Observable.create((observer) => {
let i = 1;
// Emitira vrijednost svakih 500
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
});
zip(observer_1, observer_2, observer_3).pipe(take(5));
Operatori — forkJoin
forkJoin također spaja tokove, ali emitira vrijednost samo kada su svi tokovi završeni (complete).

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emitira vrijednost svakih 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
}).pipe(take(3));
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emitira vrijednost svakih 750
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
}).pipe(take(5));
const observer_3 = Observable.create((observer) => {
let i = 1;
// Emitira vrijednost svakih 500
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
}).pipe(take(4));
forkJoin(observer_1, observer_2, observer_3);
Operatori — map
Transformacijski operator map pretvara vrijednost emitacije u novu.

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

Operatori – share, tap
Operator tap omogućuje izvršavanje side efekata, tj. bilo koje radnje koje ne utječu na tok.
Utentni operator share može pretvoriti hladni tok u vrući.

Završili smo s operatorima. Prelazimo na Subject.
Misli na glas
I tad sam otišao piti čaj. Umorili su me ti primjeri 😀
Obitelj subject-a
Obitelj subject-a je izvrstan primjer vrućih tokova. Ove klase su neka vrsta hibrida koje istovremeno djeluju kao observable i observer. Budući da je subject vrući tok, potrebno je otkazati pretplatu. Što se tiče osnovnih metoda, to su:
- next – prijenos novih podataka u tok
- error – greška i završetak toka
- complete – završetak toka
- subscribe – pretplatiti se na tok
- unsubscribe – odjaviti se s toka
- asObservable – pretvoriti u promatrača
- toPromise – pretvara u promisu
Ističe se 4 do 5 vrsta subject-a.
Misli na glas
Na streamu sam govorio o 4, a ispostavilo se da su još jedan dodali. Kako se kaže, živite i učite.
Jednostavni Subject new Subject()– najjednostavnija vrsta subject-a. Stvara se bez parametara. Prenosi vrijednosti koje dolaze samo nakon pretplate.
BehaviorSubject new BehaviorSubject( defaultData ) – po mom mišljenju najčešće korištena vrsta subject-a. Prima zadanu vrijednost. Uvijek čuva podatke posljednjeg emitiranja koje šalje prilikom pretplate. Ova klasa takođe ima koristan metod value, koji vraća trenutno stanje toka.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) – može opcionalno primiti kao prvi argument veličinu spremnika vrijednosti koje će čuvati, a kao drugi vrijeme tijekom kojeg su potrebne promjene.
AsyncSubject new AsyncSubject() – prilikom pretplate ništa se ne događa i vrijednost će biti vraćena samo prilikom complete. Vraća se samo posljednja vrijednost toka.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig | Observable, destination?: Observer) – o njemu dokumentacija šuti i ja ga prvi put vidim. Tko zna što radi, javite se, dopunit ćemo.
Uf. Pa, obradili smo sve što sam htio danas reći. Nadam se da su informacije bile korisne. Samostalno se možete informirati o popisu literature u kartici korisne informacije.
Korisne informacije
- – link na stream
- – Kako radi JS: pregled motora, mehanizama izvršenja, stoga poziva
- — Kako funkcionira JS: događajna petlja, asinkronost i pet načina za poboljšanje koda s async / await
- — Kako funkcionira Event Loop u JavaScriptu
- — Evolucija asinkronog JavaScript-a
- — Što je RxJS i zašto je korisno to znati
- — Praktična primjena RxJS-a
- — RxJS Observables Tutorial — Kreiranje & Prijavljivanje na Observables
- — RXJS: Vruće i Hladne Observables
- — Klase, funkcije za kreiranje Observable. Operatori.
- — RxJS operatori kroz primjere
- — Popis API-ja
- — Vrste Subject-a i Planovi u RxJS-u
Izvor: habr.com
