Hej alla. Det Àr Omlnytsky Sergey hÀr. För inte sÄ lÀnge sedan höll jag en ström om reaktiv programmering, dÀr jag pratade om asynkronitet i JavaScript. Idag skulle jag vilja sammanfatta detta material.

Men innan vi börjar med huvuddelen av materialet behöver vi göra en introduktion. SÄ, lÄt oss börja med definitionerna: vad Àr en stack och en kö?
Stack â Ă€r en samling dĂ€r elementen hĂ€mtas enligt principen "senast in, först ut" LIFO.
Kö â Ă€r en samling dĂ€r elementen hĂ€mtas enligt principen "först in, först ut" FIFO.
Okej, vi fortsÀtter.

JavaScript Àr ett enskilt trÄdat programmeringssprÄk. Det betyder att det bara finns en körtrÄd och en stack dÀr funktionerna placeras i kö för utförande. Följaktligen kan JavaScript vid ett givet ögonblick bara utföra en operation, medan andra operationer vÀntar pÄ sin tur i stacken tills de kallas.
Anropsstack â Ă€r en databasstruktur som, förenklat sagt, registrerar information om var vi befinner oss i programmet. NĂ€r vi gĂ„r in i en funktion, placerar vi en post om den högst upp i stacken. NĂ€r vi Ă„tervĂ€nder frĂ„n funktionen tar vi bort det översta elementet frĂ„n stacken och hamnar dĂ€r vi kallade funktionen. Det Ă€r allt som stacken kan göra. Men nu uppstĂ„r den ytterst intressanta frĂ„gan. Hur fungerar dĂ„ asynkroniteten i JavaScript?

I sjÀlva verket finns det, förutom stacken, en speciell kö i webblÀsarna för arbete med sÄ kallat WebAPI. Funktioner frÄn denna kö kommer att utföras i ordning först efter att stacken Àr helt rensad. Endast efter detta placeras de frÄn kön i stacken för utförande. Om det för nÀrvarande finns minst ett element i stacken, kan de inte komma in i stacken. Just av den anledningen Àr anrop av funktioner via timeout ofta tidsmÀssigt oexakta, eftersom funktionen inte kan komma frÄn kön till stacken sÄ lÀnge den Àr fylld.
LÄt oss titta pÄ följande exempel och gÄ igenom dess "utförande" steg för steg. LÄt oss ocksÄ titta pÄ vad som hÀnder i systemet.
console.log('Hi);
setTimeout(function cb1() {
console.log('cb1');
}, 5000);
console.log('Bye');
1) Inga hÀndelser för tillfÀllet. WebblÀsarens konsol Àr tom, anropsstacken Àr tom.

2) Sedan lÀggs kommandot console.log('Hi') till anropsstacken.

3) Och den utförs

4) Sedan tas console.log('Hi') bort frÄn anropsstacken.

5) Nu gÄr vi vidare till kommandot setTimeout(function cb1() {⊠}). Det lÀggs till i anropsstacken.

6) Kommandot setTimeout(function cb1() {⊠}) utförs. WebblÀsaren skapar en timer, som Àr en del av Web API. Den kommer att rÀkna ner tiden.

7) Kommandot setTimeout(function cb1() {⊠}) har slutfört sin uppgift och tas bort frÄn anropsstacken.

8) Kommandot console.log('Bye') lÀggs till i anropsstacken.

9) Kommandot console.log('Bye') utförs.

10) Kommandot console.log('Bye') tas bort frÄn anropsstacken.

11) NÀr minst 5000 ms har gÄtt, avslutar timern sin uppgift och placerar callback cb1 i callback-kön.

12) Event loop tar funktionen cb1 frÄn callback-kön och lÀgger den i anropsstacken.

13) Funktionen cb1 utförs och lÀgger console.log('cb1') i anropsstacken.

14) Kommandot console.log('cb1') utförs.

15) Kommandot console.log('cb1') tas bort frÄn anropsstacken.

16) Funktionen cb1 tas bort frÄn anropsstacken.
LÄt oss titta pÄ exemplet i dynamik:

Nu har vi gÄtt igenom hur asynkronitet Àr implementerat i JavaScript. LÄt oss kort diskutera evolutionen av asynkron kod.
Evolutionen av asynkron kod.
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);
})
})
})
})
})
});Asynkront programmering, som vi kÀnner det i JavaScript, kan endast genomföras av funktioner. De kan överföras som vilken annan variabel som helst till andra funktioner. SÄ föddes callbacks. Och det Àr roligt och underhÄllande, tills det förvandlas till sorg och elÀnde. Varför? Jo, helt enkelt:
- Med den ökande komplexiteten av koden, förvandlas projektet snabbt till svĂ„rbegripliga djupt inbĂ€ddade block â "callback hell".
- Felhantering kan lÀtt förbises.
- Det gÄr inte att returnera uttryck med return.
Med införandet av Promise blev lÀget nÄgot bÀttre.
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);
});- Det kom kedjor av promisser, vilket förbÀttrade kodens lÀsbarhet.
- Det kom en separat metod för att fÄnga fel.
- Möjligheten till parallell exekvering har dykt upp med hjÀlp av Promise.all
- Vi kan lösa inbÀddad asynkronitet med hjÀlp av async/await
Men ett löfte har sina begrÀnsningar. Till exempel kan ett löfte, utan extra krÄngel, inte avbrytas, och det viktigaste Àr - det fungerar med ett enda vÀrde.
Nu har vi smidigt kommit till reaktiv programmering. Trött? Tja, du kan brygga en kopp te, fundera lite och ÄtervÀnda för att lÀsa vidare. Jag fortsÀtter.

Reaktiv programmeringÀr en programmeringsparadigm inriktad pÄ datastreamar och spridning av förÀndringar. LÄt oss granska vad en datastream verkligen Àr.
// ĐĐŸĐ»ŃŃĐ°Đ”ĐŒ ŃŃŃĐ»ĐșŃ ĐœĐ° ŃĐ»Đ”ĐŒĐ”ĐœŃ
const input = ducument.querySelector('input');
const eventsArray = [];
// ĐŃŃĐžĐŒ ĐșĐ°Đ¶ĐŽĐŸĐ” ŃĐŸĐ±ŃŃОД ĐČ ĐŒĐ°ŃŃĐžĐČ eventsArray
input.addEventListener('keyup',
event => eventsArray.push(event)
);TÀnk pÄ att vi har ett inmatningsfÀlt. Vi skapar en array, och pÄ varje keyup-hÀndelse frÄn input kommer vi att lagra hÀndelsen i vÄr array. Jag vill pÄpeka att vÄr array Àr sorterad efter tid - det vill sÀga, indexet för senare hÀndelser Àr större Àn indexet för tidigare. En sÄdan array representerar en förenklad modell av en datastream, men det Àr fortfarande inte en datastream. För att denna array ska kunna kallas en datastream mÄste den kunna meddela prenumeranterna om att nya data har kommit in. PÄ sÄ sÀtt har vi kommit fram till definitionen av en datastream.
Datastream
const { interval1 } = Rx;
const { take } = RxOperators;
interval(1000).pipe(
take(4)
)
DatastreamÀr en array av data sorterade efter tid som kan meddela nÀr data har Àndrats. TÀnk nu pÄ hur bekvÀmt det blir att skriva kod dÀr en enda ÄtgÀrd krÀver att flera hÀndelser anropas pÄ olika stÀllen i koden. Vi gör helt enkelt en prenumeration pÄ datastreamen och den meddelar oss sjÀlv nÀr förÀndringar intrÀffar. Detta kan biblioteket RxJs göra.

RxJS Àr ett bibliotek för att arbeta med asynkrona och hÀndelsebaserade program med hjÀlp av observerbara sekvenser. Biblioteket tillhandahÄller den grundlÀggande typen Observable, nÄgra hjÀlptyper (Observer, Schedulers, Subjects) och operatörer för att hantera hÀndelser som samlingar (map, filter, reduce, every och liknande frÄn JavaScript Array).
LÄt oss gÄ igenom de grundlÀggande begreppen i detta bibliotek.
Observable, Observer, Producer
Observable â den första grundlĂ€ggande typen som vi kommer att titta pĂ„. Denna klass innehĂ„ller huvuddelen av implementationen av RxJs. Den Ă€r kopplad till det observerade flödet som man kan prenumerera pĂ„ med metoden subscribe.
I Observable implementeras en hjÀlpfunktion för att skapa uppdateringar, sÄ kallad Observer. KÀllan till vÀrden för Observer kallas Producer. Det kan vara en array, en iterator, web socket, nÄgot evenemang osv. SÄ man kan sÀga att observable Àr en förmedlare mellan Producer och Observer.
Observable hanterar tre typer av hÀndelser för Observer:
- next â ny data
- error â ett fel om sekvensen avslutades pĂ„ grund av ett undantag. Denna hĂ€ndelse innebĂ€r ocksĂ„ att sekvensen avslutas.
- complete â en signal om att sekvensen Ă€r avslutad. Det betyder att det inte kommer att finnas fler data.
LÄt oss se en demo:

I början kommer vi att bearbeta vÀrdena 1, 2, 3, och efter 1 sekund kommer vi att fÄ 4 och avsluta vÄrt flöde.
Tankar högt
Och hĂ€r insĂ„g jag att det var mer intressant att berĂ€tta Ă€n att skriva om det. đ
Subscription
NÀr vi prenumererar pÄ flödet skapar vi en ny klass subscription, som ger oss möjlighet att avbryta prenumerationen med metoden unsubscribe. Vi kan ocksÄ gruppera prenumerationer med metoden add. Och det Àr logiskt att vi kan avgruppera flöden med hjÀlp av ta bort. Metoderna add och remove tar en annan prenumeration som inparameter. Jag vill pÄpeka att nÀr vi avbryter en prenumeration sÄ avbryter vi ocksÄ alla underordnade prenumerationer som om vi hade anropat metoden unsubscribe pÄ dem. LÄt oss gÄ vidare.
Typer av flöden
| HOT | COLD |
|---|---|
| Producer skapas utanför observable | Producer skapas inuti observable |
| Data skickas vid skapandet av observable | Data rapporteras vid prenumerationen |
| Ytterligare logik krÀvs för avprenumeration | Flödet avslutas automatiskt |
| AnvÀnder en ett-till-mÄnga relation | AnvÀnder en ett-till-ett relation |
| Alla prenumerationer har ett gemensamt vÀrde | Prenumerationerna Àr oberoende |
| Data kan gÄ förlorad om det inte finns en prenumeration | à terutger alla vÀrden i flödet för en ny prenumeration |
Om jag skulle föra en liknelse, skulle jag jÀmföra en varm ström med en film pÄ bio. FrÄn det ögonblick du anlÀnde, började du titta. Den kalla strömmen skulle jag likna vid ett samtal till kundsupport. Den som ringer lyssnar pÄ telefonsvararens meddelande frÄn början till slut, men du kan lÀgga pÄ luren genom att avprenumerera.
Jag vill pÄpeka att det ocksÄ finns sÄ kallade varma strömmar (detta begrepp har jag sett extremt sÀllan, och dÄ endast pÄ utlÀndska forum) - det Àr en ström som transformeras frÄn kall till varm. FrÄgan uppstÄr - var ska man anvÀnda det? Jag kommer att ge ett exempel frÄn praktiken.
Jag arbetar med Angular. Det anvÀnder aktivt rxjs. För att hÀmta data frÄn servern förvÀntar jag mig en kall ström och anvÀnder denna ström i mallen med hjÀlp av asyncPipe. Om jag anvÀnder denna pipe flera gÄnger, och ÄtergÄr till definitionen av kall ström, kommer varje pipe att begÀra data frÄn servern, vilket med andra ord Àr mÀrkligt. Men om jag omvandlar den kalla strömmen till en varm, sÄ kommer begÀran att ske en gÄng.
Att förstÄ typer av strömmar Àr generellt ganska svÄrt för nybörjare, men det Àr viktigt.
Operators
return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
.pipe(
tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
map(({ data }: TradeCompanyList) => data)
);Möjligheterna att arbeta med strömmar utvidgas av operatörer. De hjÀlper till att hantera hÀndelser som passerar genom Observable. Vi kommer att titta pÄ nÄgra av de mest populÀra, och mer detaljerad information om operatörer kan hittas via lÀnkarna i den anvÀndbara informationen.
Operators â of
Vi börjar med hjÀlpoperatören of. Den skapar en Observable baserat pÄ ett enkelt vÀrde.

Operators â filter

Filteroperatören filter, som namnet antyder, filtrerar signalen i strömmen. Om operatören returnerar sant, slÀpps den igenom.
Operators â take

take - tar antalet emiterade vÀrden som följande ström kommer att avsluta efter.
Operators â debounceTime

debounceTime - kastar bort de emitterade vÀrdena som faller inom det angivna tidsintervallet mellan utsignalerna - efter det tidsintervall, emitteras det sista vÀrdet.
const { Observable } = Rx;
const { debounceTime, take } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Skickar vÀrdet var 1000 ms
setInterval(() => {
observer.next(i++)
}, 1000);
// Skickar vÀrdet var 1500 ms
setInterval(() => {
observer.next(i++)
}, 1500);
}).pipe(
debounceTime(700), // VÀntar 700 ms med vÀrden innan de bearbetas
take(3)
); 
Operatörer â takeWhile

Avger vÀrden sÄ lÀnge takeWhile inte returnerar false, efter det avregistreras frÄn strömmen.
const { Observable } = Rx;
const { debounceTime, takeWhile } = RxOperators;
Observable.create((observer) => {
let i = 1;
observer.next(i++);
// Skickar vÀrdet var 1000 ms
setInterval(() => {
observer.next(i++)
}, 1000);
}).pipe(
takeWhile(producer => producer < 5)
); 
Operatörer â combineLatest
CombineLatest-operatören liknar promise.all. Den slÄr samman flera strömmar till en. NÀr varje ström har gjort minst en avvikelse fÄr vi de senaste vÀrdena frÄn varje som en array. Efter vilket som helst avvikelse frÄn de sammanfogade strömmarna kommer den att ge nya vÀrden.

const { combineLatest, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Skickar vÀrdet var 1000 ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Skickar vÀrdet var 750 ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
combineLatest(observer_1, observer_2).pipe(take(5));
Operatörer â zip
Zip â vĂ€ntar pĂ„ vĂ€rde frĂ„n varje ström och formar en array baserat pĂ„ dessa vĂ€rden. Om inget vĂ€rde kommer frĂ„n nĂ„gon ström kommer gruppen inte att bildas.

const { zip, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// Skickar vÀrdet var 1000 ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
});
const observer_2 = Observable.create((observer) => {
let i = 1;
// Skickar vÀrdet var 750 ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
});
const observer_3 = Observable.create((observer) => {
let i = 1;
// Skickar vÀrdet var 500 ms
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
});
zip(observer_1, observer_2, observer_3).pipe(take(5));
Operatörer â forkJoin
forkJoin slÄr ocksÄ samman strömmarna, men den avger vÀrden endast nÀr alla strömmar har avslutats (komplett).

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;
const observer_1 = Observable.create((observer) => {
let i = 1;
// SÀnder vÀrdet var 1000ms
setInterval(() => {
observer.next('a: ' + i++);
}, 1000);
}).pipe(take(3));
const observer_2 = Observable.create((observer) => {
let i = 1;
// SÀnder vÀrdet var 750ms
setInterval(() => {
observer.next('b: ' + i++);
}, 750);
}).pipe(take(5));
const observer_3 = Observable.create((observer) => {
let i = 1;
// SÀnder vÀrdet var 500ms
setInterval(() => {
observer.next('c: ' + i++);
}, 500);
}).pipe(take(4));
forkJoin(observer_1, observer_2, observer_3);
Operatorer â map
Transformationsoperatorn map omvandlar vÀrdet som emitteras till ett nytt.

const { Observable } = Rx;
const { take, map } = RxOperators;
Observable.create((observer) => {
let i = 1;
// SÀnder vÀrdet var 1000ms
setInterval(() => {
observer.next(i++);
}, 1000);
}).pipe(
map(x => x * 10),
take(3)
);

Operatorer â share, tap
Operatorn tap tillÄter sid-effekter, det vill sÀga ÄtgÀrder som inte pÄverkar sekvensen.
HjÀlpoperatorn share kan göra en kallström till en varmström.

Vi Àr klara med operatorerna. LÄt oss gÄ vidare till Subject.
Tankar högt
Och hĂ€r gick jag för att dricka te. Dessa exempel tröttade ut mig đ
Familjen subject-er
Familjen subject-er Àr ett tydligt exempel pÄ varma strömmar. Dessa klasser Àr en slags hybrid som fungerar bÄde som observable och observer. Eftersom subject Àr en varmström, mÄste man avregistrera sig frÄn den. De huvudsakliga metoderna Àr:
- next â skicka nya data till strömmen
- error â fel och avslutning av strömmen
- complete â avslutning av strömmen
- subscribe â prenumerera pĂ„ strömmen
- unsubscribe â avregistrera sig frĂ„n strömmen
- asObservable â transformera till en observer
- toPromise â omvandlar till ett löfte
Det finns 4â5 typer av subject-er.
Tankar högt
PÄ streamen pratade jag om 4, men sedan visade det sig att de hade lagt till en till. Som man sÀger, lÀr sig nÄgot nytt varje dag.
Enkel Subject new Subject()â den mest grundlĂ€ggande typen av subject-er. Skapas utan parametrar. Skickar vĂ€rden som kommer efter registrering.
BehaviorSubject new BehaviorSubject(defaultData) â enligt mig den mest spridda typen av subject-er. Tar ett standardvĂ€rde som argument. Sparar alltid det senaste emiterade vĂ€rdet som den skickar vid registrering. Denna klass har ocksĂ„ en anvĂ€ndbar metod, value, som returnerar det aktuella vĂ€rdet i strömmen.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) â kan som första argument valfritt ta storleken pĂ„ buffern av vĂ€rden som den kommer att lagra och som andra argument tiden under vilken vi behöver Ă€ndringar.
AsyncSubject ny AsyncSubject() â vid prenumeration hĂ€nder ingenting, och vĂ€rdet kommer att returneras endast vid complete. Endast det senaste vĂ€rdet frĂ„n strömmen kommer att returneras.
WebSocketSubject ny WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) â Dokumentationen nĂ€mner inte detta och jag ser det för första gĂ„ngen sjĂ€lv. Den som vet vad det gör, skriv gĂ€rna sĂ„ vi kan komplettera.
Puh. NÄvÀl, vi har nu gÄtt igenom allt jag ville berÀtta idag. Jag hoppas att denna information har varit nyttig. Du kan sjÀlv bekanta dig med litteraturlistan under fliken nyttig information.
Nyttig information
- â lĂ€nk till streamen
- â Hur JS fungerar: översikt av motorn, körrutiner, samt anropsstackar
- â Hur JS fungerar: evenemangslopp, asynkronitet och fem sĂ€tt att förbĂ€ttra koden med hjĂ€lp av async / await
- â Hur Event Loop fungerar i JavaScript
- â Utvecklingen av asynkront JavaScript
- â Vad Ă€r RxJS och varför Ă€r det bra att kĂ€nna till det
- â Praktisk tillĂ€mpning av RxJS
- â RxJS Observables Tutorial â Skapa & Prenumerera pĂ„ Observables
- â RXJS: Varma och kalla Observables
- â Klasser, funktioner för att skapa Observable. Operatörer.
- â RxJS-operatörer med exempel
- â API-lista
- â Olika slags Subject och Scheman i RxJS
KĂ€lla: habr.com
