Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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?

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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');

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

1) Inga hÀndelser för tillfÀllet. WebblÀsarens konsol Àr tom, anropsstacken Àr tom.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

3) Och den utförs

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

16) Funktionen cb1 tas bort frÄn anropsstacken.

LÄt oss titta pÄ exemplet i dynamik:

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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)
)

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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:

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

HOTCOLD
Producer skapas utanför observableProducer skapas inuti observable
Data skickas vid skapandet av observableData rapporteras vid prenumerationen
Ytterligare logik krÀvs för avprenumerationFlödet avslutas automatiskt
AnvÀnder en ett-till-mÄnga relationAnvÀnder en ett-till-ett relation
Alla prenumerationer har ett gemensamt vÀrdePrenumerationerna À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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

Operators — filter

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Operators — take

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

Operators — debounceTime

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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)
);  

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

Operatörer — takeWhile

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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)
);  

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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));

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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));

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

Operatörer — forkJoin

forkJoin slÄr ocksÄ samman strömmarna, men den avger vÀrden endast nÀr alla strömmar har avslutats (komplett).

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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);

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

Operatorer — map

Transformationsoperatorn map omvandlar vÀrdet som emitteras till ett nytt.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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)
);

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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.

Asynkron programmering i JavaScript. (Callback, Promise, RxJs)

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

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster