Tere kĂ”igile. Siin on Omelnicki Sergei. Hiljuti viis ma lĂ€bi voogesituse reaktiivse programmeerimise teemal, kus rÀÀkisin asĂŒnkroonsusest JavaScriptis. TĂ€na sooviksin selle materjali kokku vĂ”tta.

Aga enne, kui alustame pÔhiteemat, peame tegema sissejuhatuse. Niisiis, alustame definitsioonidest: mis on virn ja jÀrjekord?
Virn â see on kollektsioon, mille elemendid saadakse "viimane sisse, esimene vĂ€lja" LIFO pĂ”himĂ”ttel.
JĂ€rjekord â see on kollektsioon, mille elemendid saadakse "esimene sisse, esimene vĂ€lja" FIFO pĂ”himĂ”ttel.
Okei, jÀtkame.

JavaScript on ĂŒhesuunaline programmeerimiskeel. See tĂ€hendab, et sellel on ainult ĂŒks tĂ€itmisprotsess ja ĂŒks virn, kuhu funktsioonid jĂ€etakse tĂ€itmiseks ootele. Seega vĂ”ib JavaScript korraga sooritada ainult ĂŒhe operatsiooni, teised operatsioonid ootavad oma jĂ€rjekorda virnas, kuni neid kutsutakse.
Kutsumisvirn â see on andme struktuur, mis lihtsustatult öeldes salvestab teavet programmikohtade kohta, kus me asume. Kui me lĂ€heme funktsiooni, paneme selle kohta kirje virna tippu. Kui me funktsioonist tagasi tuleme, tĂ”mbame virnast vĂ€lja kĂ”ige ĂŒlemise elemendi ja leiame end sealt, kust seda funktsiooni kutsuti. See on kĂ”ik, mida virn oskab. Ja nĂŒĂŒd on vĂ€ga huvitav kĂŒsimus. Kuidas siis toimib asĂŒnkroonsus JavaScriptis?

Tegelikult on lisaks virnale brauserites eriline jĂ€rjekord, mis tegeleb nii nimetatud WebAPI-ga. Selle jĂ€rjekorra funktsioonid tĂ€idetakse jĂ€rjestikku ainult pĂ€rast seda, kui virn on tĂ€ielikult tĂŒhi. Alles siis paigutatakse need jĂ€rjekorrast virna tĂ€itmiseks. Kui virnas on hetkel vĂ€hemalt ĂŒks element, ei saa nad virna sattuda. Just sellepĂ€rast on ajavahemiku jĂ€rgi kutse sageli ajaliselt ebatĂ€pne, kuna funktsioon ei saa minna jĂ€rjekorrast virna, kuni see on tĂ€is.
Vaatame jĂ€rgmist nĂ€idet ja teeme selle samm-sammult 'tĂ€itmise'. Samuti vaatame, mis sel ajal sĂŒsteemis toimub.
console.log('Hi);
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
1) Praegu ei toimu midagi. Brauseri konsool on tĂŒhi, kutse virn on tĂŒhi.

2) Siis lisatakse kĂ€sk console.log(âHiâ) kutse virna.

3) Ja see tÀidetakse.

4) Siis eemaldatakse console.log(âHiâ) kutse virnast.

5) NĂŒĂŒd liigume kĂ€su setTimeout(function cb1() {⊠}). juurde. See lisatakse kutse virna.

6) KÀsk setTimeout(function cb1() {⊠}) tÀidetakse. Brauser loob ajastuse, mis on osa Web API-st. See hakkab aega maha arvestama.

7) KÀsk setTimeout(function cb1() {⊠}) on lÔpetanud oma töö ja eemaldatakse kutse virnast.

8) KĂ€sk console.log(âByeâ) lisatakse kutse virna.

9) KĂ€sk console.log(âByeâ) tĂ€idetakse.

10) KĂ€sk console.log(âByeâ) eemaldatakse kutse virnast.

11) PÀrast seda, kui on möödunud vÀhemalt 5000 ms., lÔpetab ajastamine töö ja paneb tagaside cb1 tagasi tagaside jÀrjekorda.

12) Ărituste tsĂŒkkel vĂ”tab funktsiooni cb1 tagaside jĂ€rjekorrast ja paneb selle kutse virna.

13) Funktsioon cb1 tĂ€idetakse ja lisab console.log(âcb1â) kutse virna.

14) KĂ€sk console.log(âcb1â) tĂ€idetakse.

15) KĂ€sk console.log(âcb1â) eemaldatakse kutse virnast.

16) Funktsioon cb1 eemaldatakse kutse virnast.
Vaadakem dĂŒnaamikat nĂ€ite nĂ€itel:

Nii oleme arutanud, kuidas JavaScriptis asĂŒnkroonsust rakendatakse. NĂŒĂŒd rÀÀgime lĂŒhidalt asĂŒnkroonse koodi arengust.
AsĂŒnkroonse koodi areng.
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);
})
})
})
})
})
});AsĂŒnkroonset programmeerimist, nagu me seda JavaScriptis tunneme, saab rakendada ainult funktsioonide kaudu. Neid saab edastada teistele funktsioonidele nagu mistahes teised muutujad. Nii said alguse tagasihelbid. See on lahe, lĂ”bus ja toretsev, kuni see muutub kurvaks ja masendavaks. Miks? KĂ”ik on lihtne:
- Koodi keerukuse kasvuga muutub projekt kiiresti arusaamatuks sĂŒgavalt pesitsevateks plokkideks â âtagasikutsumise pĂ”rguâ.
- Vead vÔib kergesti tÀhelepanuta jÀtta.
- VĂ€ljendeid ei saa returni kaudu tagasi anda.
Promise'i saabumisega muutus olukord veidi paremaks.
uus Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 2000);
}).then((result) => {
alert(result);
return result + 2;
}).then((result) => {
throw new Error('SIIN EBAUS');
alert(result);
return result + 2;
}).then((result) => {
alert(result);
return result + 2;
}).catch((e) => {
console.log('viga: ', e);
});- Aktiivne Promise'a ahel tÀiendab koodi arusaadavust
- Sisse tuli eraldi veateate kÀsitsemise meetod
- Ilmus vÔimalus paralleelseks tÀitmiseks Promise.all abil
- SĂŒvenenud asĂŒnkroonsust saame lahendada async/await abil
Kuid Promise'il on oma piirangud. NĂ€iteks ei saa seda tantsimata tĂŒhistada ja mis kĂ”ige tĂ€htsam, see töötab ĂŒhe vÀÀrtusega.
NĂŒĂŒd oleme sujuvalt jĂ”udnud reaktiivse programmeerimise juurde. Kas oled vĂ€sinud? Kuid Ă”nneks saad minna teed tegema, mĂ”tle ja naase edasi lugema. Mina jĂ€tkan.

Reaktiivne programmeerimineâââprogrammimise paradigma, mis on suunatud andmevoogudele ja muudatuste levikule. Vaatame lĂ€hemalt, mis on andmevoog.
// ĐĐŸĐ»ŃŃĐ°Đ”ĐŒ ŃŃŃĐ»ĐșŃ ĐœĐ° ŃĐ»Đ”ĐŒĐ”ĐœŃ
const input = ducument.querySelector('input');
const eventsArray = [];
// ĐŃŃĐžĐŒ ĐșĐ°Đ¶ĐŽĐŸĐ” ŃĐŸĐ±ŃŃОД ĐČ ĐŒĐ°ŃŃĐžĐČ eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);Kujutage ette, et meil on sisestusala. Loome massiivi ja iga keyup sĂŒndmuse puhul salvestame sĂŒndmuse meie massiivi. Oluline on mĂ€rkida, et meie massiiv on jĂ€rjestatud aja pĂ”hjal, s.t. hilisemate sĂŒndmuste indeks on suurem kui varasemate indeks. Selline massiiv esindab lihtsustatud andmevoogu, kuid see ei ole veel voog. Selle massiivi rahuldavaks nimetamiseks vooguks peab see suutma mingil viisil teavitada tellijaid, et sellesse on lisandunud uusi andmeid. Nii jĂ”uame voogude mÀÀratlemise juurde.
Andmevoog
const { interval1 } = Rx;
const { take } = RxOperators;
interval(1000).pipe(
take(4)
)
Voogon aja jĂ€rgi jĂ€rjestatud andmemassiiv, mis suudab teavitada, et andmed on muutunud. Kujutage nĂŒĂŒd ette, kui mugav on kirjutada koodi, kus ĂŒhe tegevuse jaoks tuleb kutsuda mitmeid sĂŒndmusi erinevates koodilĂ”ikudes. Lihtsalt tellime voogu ja see teavitab meid iseseisvalt, kui muudatused toimuvad. Seda oskab teha RxJs raamatukogu.

RxJS â on asĂŒnkroonsete ja sĂŒndmustel pĂ”hinevate programmide töötamiseks mĂ”eldud teek, mis kasutab jĂ€lgitavaid jĂ€rjestusi. Teek pakub pĂ”hiliiki Observable, mitu abiliiki (Observer, Schedulers, Subjects) ja sĂŒndmustega töötamise operaatorid, nagu kollektsioonid (map, filter, reduce, every ja sarnased JavaScripti massiivist).
Vaatame selle teegi pÔhikontseptsioone.
Observable, Observer, Producer
Observable â esimene pĂ”hitĂŒĂŒp, mida kĂ€sitleme. See klass sisaldab RxJs-i peamist rakenduse osa. See on seotud jĂ€lgitava vooga, millele saab tellida, kasutades meetodit subscribe.
Observable-is on rakendatud abimehhanism vĂ€rskenduste loomiseks, mida nimetatakse Observer. VÀÀrsuse allikaks Observerile on Producer. See vĂ”ib olla massiiv, iterator, veebiteenus, mĂ”ni sĂŒndmus jne. Seega vĂ”ib öelda, et observable toimib juhina Produceri ja Observeri vahel.
Observable töötleb kolme tĂŒĂŒpi sĂŒndmusi Observeri jaoks:
- next â uued andmed
- error â viga, kui jĂ€rjestus lĂ”petatakse erandliku olukorra tĂ”ttu. See sĂŒndmus tĂ€hendab samuti jĂ€rjestuse lĂ”petamist.
- complete â seeria lĂ”petamise signaal. See tĂ€hendab, et uusi andmeid enam ei tule.
Vaata demot:

Alguses töötleme vÀÀrtusi 1, 2, 3, ja pÀrast 1 sekundi möödumist saame 4 ning lÔpetame meie voogu.
MÔtted valjult
Ja siis mĂ”istsin, et rÀÀkida oli huvitavam kui kirjutada sellest. đ
Tellimus
Kui me teeme tellimuse voole, loome uue klassi subscription, mis annab meile vĂ”imaluse tellimus lĂ”petada meetodi kaudu unsubscribe. Samuti saame tellimusi gruppidesse panna meetodi abil add. Loomulikult saame vooge eemaldada meetodi kaudu remove. Meetodid add ja remove vĂ”tavad sisendiks teise tellimuse. Tahan mĂ€rkida, et kui me tĂŒhistame tellimuse, tĂŒhistame kĂ”ik lapse tellimused nagu oleksime neis meetodi unsubscribe kutsunud. JĂ€tkame.
Vood tĂŒĂŒbid
| HOT | COLD |
|---|---|
| Produtsent luuakse observable'i vÀljastpoolt | Produtsent luuakse observable'i sees |
| Andmed edastatakse observable'i loomise hetkel | Andmed teatatakse tellimise hetkel |
| Vajalik on lisaloogika tellimuse tĂŒhistamiseks | Voog lĂ”peb iseseisvalt |
| Kasutab ĂŒks-mitmele seost | Kasutab ĂŒks-ĂŒhele seost |
| KĂ”ik tellimused omavad ĂŒhte vÀÀrtust | Tellimused on sĂ”ltumatud |
| Andmed vÔivad kaduda, kui tellimust pole | Taaskavandab kÔik voolu vÀÀrtused uue tellimuse jaoks |
Kui tuua analoogia, siis kujutaksin kuuma voogu nagu filmi kinosaalis. Mis iganes ajahetkel sa sisse astud, sellest hetkest alates hakkad vaatama. KĂŒlma voogu vĂ”rdleksin helistamisega tehnilisse tugi. Iga helistaja kuuleb salvestust algusest lĂ”puni, kuid saad lopendada kĂ”ne unsubscribe abil.
Tahaksin mĂ€rkida, et eksisteerivad ka nn soojad vood (sellist mÀÀratlust olen kohanud ÀÀrmiselt harva ja vaid vĂ€lismaistes kogukondades) â see on voog, mis muundatakse kĂŒlmast voost kuumaks. Kuidas seda kasutada? Toona toon nĂ€ite praktikas.
Ma töötan Angulariga. See kasutab aktiivselt RxJS'i. Andmete saamiseks serverist ootan kĂŒlma voogu ja kasutan seda ĆĄabloonis asyncPipe'i abil. Kui kasutan seda pipe'i mitu korda, siis, naastes kĂŒlma voo mÀÀratlemise juurde, kĂŒsib iga pipe andmeid serverist, mis on pehmelt öeldes kummaline. Kui aga muudan kĂŒlma voo soojaks, toimub pĂ€ring vaid korra.
Tegelikult on voogude tĂŒĂŒpide mĂ”istmine algajatele ĂŒsna keeruline, kuid oluline.
Operaatoreid
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);Asetuse töötlemiseks voogudega vĂ”imaldab meile operaatorite kasutamine. Need aitavad juhtida sĂŒndmusi, mis voolavad Observable'is. Tutvume mĂ”ne populaarse operaatoriga, detailsemalt operaatorite kohta on teavet kasulikku informatsiooni linkides.
Operaatorid â of
Alustame abistava operaatoriga of. See loob Observable'i lihtsast vÀÀrtusest.

Operaatorid â filter

Filter operaator filter, nagu nimest jÀreldada, filtreerib voolu signaali. Kui operaator tagastab tÔe, siis lubab edasised signaalid lÀbi.
Operaatorid â take

take â VĂ”tab arvesse emissioonide arvu, pĂ€rast mida voog lĂ”ppeb.
Operaatorid â debounceTime

debounceTime â filtreerib vĂ€lja emissioonid, mis esinevad mÀÀratud ajavahemikus, emitades viimase vÀÀrtuse pĂ€rast ajavahemiku lĂ”ppu.
const { Observable } = Rx;
const { debounceTime, take } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Emiteerib vÀÀrtuse iga 1000ms jÀrel
setInterval(() => {
observer.next(i++)
}, 1000);
// Emiteerib vÀÀrtuse iga 1500ms jÀrel
setInterval(() => {
observer.next(i++)
}, 1500);
}).pipe(
debounceTime(700), // Ootame 700ms, enne kui töötleme vÀÀrtuseid
take(3)
); 
Operaatorid â takeWhile

Emitab vÀÀrtusi seni, kuni takeWhile ei tagasta false, pÀrast mida lÔpetab voost.
const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Emiteerib vÀÀrtuse iga 1000ms jÀrel
setInterval(() => {
observer.next(i++)
}, 1000);
}).pipe(
takeWhile(producer => producer < 5)
); 
Operaatorid â combineLatest
Kombineeritud operaator combineLatest on sarnane promise.all-iga. See ĂŒhendab mitu voogu ĂŒheks. Kui iga voog on vĂ€hemalt korra vÀÀrtuse edastanud, saame viimased vÀÀrtused igast voost massiivina. Edasi, peale igat vÀÀrtuse edastamist ĂŒhendatud voogudest, hakkab see andma uusi vÀÀrtusi.

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Edastame vÀÀrtuse iga 1000 ms jÀrel
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Edastame vÀÀrtuse iga 750 ms jÀrel
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
combineLatest(observer_1, observer_2).pipe(take(5));
Operaatorid â zip
Zip â ootab vÀÀrtust igast voost ja loob massiivi nende vÀÀrtuste pĂ”hjal. Kui vÀÀrtus ei saabu mĂ”nest voost, siis gruppi ei moodustata.

const { zip, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emit value every 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emit value every 750
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
const observer_3 = Observable.create((observer) => {
let i = 1;
// Emit value every 500
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
});
zip(observer_1, observer_2, observer_3).pipe(take(5));
Operators â forkJoin
forkJoin ĂŒhendab vooge, kuid emiteerib vÀÀrtuse ainult siis, kui kĂ”ik vood on lĂ”petatud (complete).

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Emiteerime vÀÀrtuse iga 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
}).pipe(take(3));
const observer_2 = Observable.create((observer) => {
let i = 1;
// Emiteerime vÀÀrtuse iga 750
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
}).pipe(take(5));
const observer_3 = Observable.create((observer) => {
let i = 1;
// Emiteerime vÀÀrtuse iga 500
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
}).pipe(take(4));
forkJoin(observer_1, observer_2, observer_3);
Operators â map
Transformatsiooni operaator map muundab emiteeritava vÀÀrtuse uueks.

const { Observable } = Rx;
const { take, map } = RxOperators;
Observable.create((observer) => {
let i = 1;
// VÀljastame vÀÀrtuse iga 1000 ms tagant
setInterval(() => {
observer.next(i++);
}, 1000);
}).pipe(
map(x => x * 10),
take(3)
);

Operaatoreid â share, tap
Tap operaator vĂ”imaldab teostada kĂŒlgtoimeid, st mingeid tegevusi, mis ei mĂ”juta jĂ€rjestust.
Utilitaarne operaator share suudab kĂŒlmast voogust teha sooja voolu.

Ooperaatoreid on piisavalt. Liigume Subjecti juurde.
MÔtted valjult
Ja siis lĂ€ksin teed joogile. Need nĂ€ited vĂ€sitavad mind đ
Subjectite perekond
Subjectite perekond on selge nĂ€ide sooja voolu. Need klassid on omamoodi hĂŒbriid, mis toimivad samaaegselt nii observableâina kui observerâina. Kuna subject on soe vool, tuleb sellest Ă€ra tellida. Peamiste meetodite hulka kuuluvad:
- next â edastamine uusi andmeid voolu
- error â viga ja voolu lĂ”petamine
- complete â voolu lĂ”petamine
- subscribe â voolule tellimine
- unsubscribe â voolust loobumine
- asObservable â transformeerime observerâiks
- toPromise â transformeerib promiseks
Erinevate subjectite tĂŒĂŒpe on 4-5.
MÔtted valjult
Streamâis rÀÀkisin 4-st, aga selgus, et nad on veel ĂŒhe lisanud. Nagu öeldakse, elad ja Ă”pid.
Lihtne Subject new Subject()â ainus lihtsaim subject-tĂŒĂŒp. Luua saab ilma parameetriteta. Edastab vÀÀrtusi, mis saabuvad ainult pĂ€rast tellimist.
BehaviorSubject new BehaviorSubject(defaultData) â minu arvates kĂ”ige levinum subject-tĂŒĂŒp. VĂ”tab sisendina vastu vaikimisi vÀÀrtuse. Salvestab alati viimase emit'i andmed, mida ta edastab tellimise ajal. Sellel klassil on ka kasulik meetod value, mis tagastab voogude praeguse vÀÀrtuse.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) â VĂ”ib esimesena argumentina valida optimaalselt vÀÀrtuste puhversuuruse, mida ta endas salvestab, ja teiseks aja, mille jooksul me muudatusi vajame.
AsyncSubject new AsyncSubject() â Tellimise ajal ei juhtu midagi ning vÀÀrtus tagastatakse alles siis, kui see on complete. Tagastatakse ainult voogude viimane vÀÀrtus.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig | Observable, destination?: Observer) â Selle kohta ei rÀÀgi dokumentatsioon ning nĂ€en seda esmakordselt. Kes teab, mida see teeb, kirjutage, tĂ€iustame.
Uhh. NĂŒĂŒd oleme kĂ€sitlenud kĂ”ike, mida tahtsin tĂ€na rÀÀkida. Loodan, et see teave oli kasulik. Iseseisvalt saab kirjanduse nimekirjaga tutvuda kasuliku teabe vahekaardil.
Kasulik teave
- â link voogudele
- â Kuidas JS töötab: mootori, kĂ€itamise mehhanismide, kutsumisstacki ĂŒlevaade
- â Kuidas JS töötab: sĂŒndmuste tsĂŒkkel, asĂŒnkroonsus ja viis viisi koodi parandamiseks async / await abil
- â Kuidas töötab Event Loop JavaScriptis
- â AsĂŒnkroonse JavaScripti evolutsioon
- â Mis on RxJS ja miks on seda kasulik teada
- â RxJS praktiline rakendamine
- â RxJS Observables Tutorial â Creating & Subscribing to Observables
- â RXJS: kuumad ja kĂŒlmad observables
- â Klassid, funktsioonid Observable'i loomiseks. Operaatorid.
- â RxJS operaatorid nĂ€idise kaudu
- â API nimekiri
- â Subject'i tĂŒĂŒbid ja ajakavad RxJS-is
Allikas: habr.com
