Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Hej alle. Kontakt Omelnitsky Sergey. For ikke så længe siden var jeg vært for en stream om reaktiv programmering, hvor jeg talte om asynkroni i JavaScript. I dag vil jeg gerne opsummere dette materiale.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Men før vi starter hovedmaterialet, skal vi lave en introduktion. Så lad os starte med definitioner: hvad er stak og kø?

Stak er en samling, hvis elementer hentes på "sidst ind, først ud" LIFO basis

Tur er en samling, hvis elementer er opnået efter princippet ("først ind, først ud" FIFO

Okay, lad os fortsætte.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

JavaScript er et enkelt-trådet programmeringssprog. Det betyder, at den kun har én udførelsestråd og én stak, hvor funktioner er i kø for udførelse. Derfor kan JavaScript kun udføre én handling ad gangen, mens andre operationer vil vente på deres tur på stakken, indtil de kaldes.

opkaldsstabel er en datastruktur, der helt enkelt registrerer information om det sted i programmet, hvor vi er. Hvis vi hopper ind i en funktion, skubber vi dens indgang til toppen af ​​stakken. Når vi vender tilbage fra en funktion, springer vi det øverste element fra stakken og ender der, hvor vi kaldte denne funktion fra. Det er alt, hvad stakken kan gøre. Og nu et meget interessant spørgsmål. Hvordan fungerer asynkroni så i JavasScript?

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Faktisk har browsere udover stakken en særlig kø til at arbejde med det såkaldte WebAPI. Funktioner fra denne kø vil først blive udført i rækkefølge, efter at stakken er fuldstændig ryddet. Først derefter placeres de fra køen på stakken til udførelse. Hvis der er mindst ét ​​element på stakken i øjeblikket, så kan de ikke komme på stakken. Netop derfor er det ofte unøjagtigt at kalde funktioner efter timeout, da funktionen ikke kan komme fra køen til stakken, mens den er fuld.

Lad os tage et kig på følgende eksempel, og lad os gennemgå det trin for trin. Lad os også se, hvad der sker i systemet.

console.log('Hi');
setTimeout(function cb1() {
    console.log('cb1');
}, 5000);
console.log('Bye');

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

1) Indtil videre er der ikke sket noget. Browserkonsollen er ren, opkaldsstakken er tom.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

2) Derefter tilføjes kommandoen console.log('Hi') til opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

3) Og det er opfyldt

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

4) Derefter fjernes console.log('Hi') fra opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

5) Lad os nu gå videre til kommandoen setTimeout(funktion cb1() {... }). Det føjes til opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

6) Kommandoen setTimeout(funktion cb1() {... }) udføres. Browseren opretter en timer, der er en del af Web API. Det vil udføre en nedtælling.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

7) Kommandoen setTimeout(funktion cb1() {... }) har fuldført sit arbejde og fjernes fra opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

8) Kommandoen console.log('Bye') føjes til opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

9) Kommandoen console.log('Bye') udføres.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

10) Kommandoen console.log('Bye') fjernes fra opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

11) Efter mindst 5000 ms er gået, slutter timeren og sætter cb1-tilbagekaldet i tilbagekaldskøen.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

12) Hændelsesløkken tager funktionen cb1 fra tilbagekaldskøen og skubber den ind på opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

13) cb1-funktionen udføres og tilføjer console.log('cb1') til opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

14) Kommandoen console.log('cb1') udføres.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

15) Kommandoen console.log('cb1') fjernes fra opkaldsstakken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

16) Funktion cb1 fjernes fra opkaldsstakken.

Lad os se på et eksempel i dynamik:

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Nå, vi så på, hvordan asynkroni er implementeret i JavaScript. Lad os nu tale kort om udviklingen af ​​asynkron kode.

Udviklingen af ​​asynkron 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);
                    })
                })
            })
        })
    })
});

Asynkron programmering, som vi kender det i JavaScript, kan kun udføres med funktioner. De kan overføres som enhver anden variabel til andre funktioner. Sådan blev tilbagekald født. Og det er fedt, sjovt og inderligt, indtil det bliver til tristhed, melankoli og tristhed. Hvorfor? Ja, det er enkelt:

  • Efterhånden som kompleksiteten af ​​koden vokser, bliver projektet hurtigt til obskure flere indlejrede blokke - "tilbagekaldshelvede".
  • Fejlhåndtering kan let overses.
  • Du kan ikke returnere udtryk med retur.

Med fremkomsten af ​​Promise er situationen blevet lidt bedre.

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

  • Der dukkede løftekæder op, hvilket forbedrede kodens læsbarhed
  • Der var en særskilt metode til aflytning af fejl
  • Parallel udførelse med Promise.all tilføjet
  • Vi kan løse indlejret asynkroni med async/await

Men løftet har sine begrænsninger. For eksempel kan et løfte, uden at danse med en tamburin, ikke annulleres, og vigtigst af alt fungerer det med én værdi.

Nå, her nærmer vi os gnidningsløst reaktiv programmering. Træt? Nå, det gode er, at du kan gå hen for at brygge nogle måger, brainstorme og vende tilbage for at læse mere. Og jeg vil fortsætte.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Reaktiv programmering - et programmeringsparadigme med fokus på datastrømme og udbredelse af ændringer. Lad os se nærmere på, hvad en datastrøm er.

// Получаем ссылку на элемент
const input = ducument.querySelector('input');

const eventsArray = [];

// Пушим каждое событие в массив eventsArray
input.addEventListener('keyup',
    event => eventsArray.push(event)
);

Lad os forestille os, at vi har et inputfelt. Vi opretter et array, og for hver indtastning af inputhændelsen gemmer vi hændelsen i vores array. Samtidig vil jeg bemærke, at vores array er sorteret efter tid, dvs. indekset for senere begivenheder er større end indekset for tidligere. Et sådant array er en forenklet dataflowmodel, men det er endnu ikke et flow. For at dette array sikkert kan kaldes en stream, skal det på en eller anden måde kunne informere abonnenter om, at der er kommet nye data i det. Dermed kommer vi til definitionen af ​​flow.

Datastrøm

const { interval } = Rx;
const { take } = RxOperators;

interval(1000).pipe(
    take(4)
)

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

stream er en række data sorteret efter tid, der kan indikere, at dataene er ændret. Forestil dig nu, hvor praktisk det bliver at skrive kode, hvor du skal udløse flere hændelser i forskellige dele af koden for én handling. Vi abonnerer blot på strømmen, og den fortæller os, når der sker ændringer. Og RxJs-biblioteket kan gøre dette.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

RxJS er et bibliotek til at arbejde med asynkrone og begivenhedsbaserede programmer ved hjælp af observerbare sekvenser. Biblioteket leverer hovedtypen observerbare, flere hjælpertyper (Observatører, skemalæggere, emner) og operatører til at arbejde med begivenheder som med samlinger (kort, filtrer, reducer, hver og lignende fra JavaScript Array).

Lad os forstå de grundlæggende begreber i dette bibliotek.

Observerbar, iagttager, producent

Observerbar er den første basetype, vi vil se på. Denne klasse indeholder hoveddelen af ​​RxJs-implementeringen. Den er knyttet til en observerbar strøm, som kan abonneres på ved hjælp af abonneringsmetoden.

Observable implementerer en hjælpemekanisme til at skabe opdateringer, den såkaldte Observer. Kilden til værdier for en observatør kaldes Producer. Det kan være et array, en iterator, en web-socket, en slags begivenhed osv. Så vi kan sige, at observable er en dirigent mellem producent og observatør.

Observable håndterer tre slags Observer-begivenheder:

  • næste - nye data
  • fejl - en fejl, hvis sekvensen blev afsluttet på grund af en undtagelse. denne begivenhed indebærer også slutningen af ​​sekvensen.
  • komplet - et signal om slutningen af ​​sekvensen. Det betyder, at der ikke kommer flere nye data

Lad os se en demo:

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

I begyndelsen behandler vi værdierne 1, 2, 3 og efter 1 sek. vi får 4 og afslutter vores tråd.

Tænker højt

Og så gik det op for mig, at det var mere interessant at fortælle end at skrive om det. 😀

Abonnement

Når vi abonnerer på en stream, opretter vi en ny klasse abonnement, hvilket giver os mulighed for at afmelde med metoden afmeld. Vi kan også gruppere abonnementer ved hjælp af metoden tilføje. Nå, det er logisk, at vi kan opdele tråde ved hjælp af Fjern. Tilføj og fjern metoderne accepterer et andet abonnement som input. Jeg vil gerne bemærke, at når vi afmelder, afmelder vi alle underordnede abonnementer, som om de også kaldte afmeldingsmetoden. Fortsæt.

Typer af vandløb

HOT
KOLD

Producer er skabt uden for det observerbare
Producer er skabt inde observerbar

Data videregives på det tidspunkt, hvor det observerbare oprettes
Data leveres på abonnementstidspunktet.

Brug for mere logik for at afmelde
Tråden slutter af sig selv

Bruger et en-til-mange forhold
Bruger et en-til-en forhold

Alle abonnementer har samme værdi
Abonnementer er uafhængige

Data kan gå tabt, hvis der ikke er noget abonnement
Genudsender alle streamværdier for et nyt abonnement

For at give en analogi, ville jeg forestille mig en varm stream som en film i en biograf. På hvilket tidspunkt du kom, fra det øjeblik begyndte du at se. Jeg vil sammenligne en kold strøm med et opkald i dem. support. Enhver, der ringer, lytter til telefonsvarerens optagelse fra start til slut, men du kan lægge på ved at afmelde.

Jeg vil gerne bemærke, at der også er såkaldte varme vandløb (jeg har mødt en sådan definition yderst sjældent og kun i fremmede samfund) - dette er et vandløb, der forvandles fra et koldt vandløb til et varmt. Spørgsmålet opstår - hvor skal man bruge)) Jeg vil give et eksempel fra praksis.

Jeg arbejder med Angular. Han bruger aktivt rxjs. For at få data til serveren forventer jeg en kold stream, og jeg bruger denne stream i skabelonen ved hjælp af asyncPipe. Hvis jeg bruger dette rør flere gange, vil hvert rør, når jeg vender tilbage til definitionen af ​​en kold strøm, anmode om data fra serveren, hvilket mildest talt er mærkeligt. Og hvis jeg konverterer en kold strøm til en varm, så sker anmodningen én gang.

Generelt er det ret svært for begyndere at forstå typen af ​​flows, men vigtigt.

Operatører

return this.http.get(`${environment.apiUrl}/${this.apiUrl}/trade_companies`)
    .pipe(
        tap(({ data }: TradeCompanyList) => this.companies$$.next(cloneDeep(data))),
        map(({ data }: TradeCompanyList) => data)
    );

Operatører giver os mulighed for at arbejde med streams. De hjælper med at kontrollere begivenhederne, der flyder i det observerbare. Vi vil overveje et par af de mest populære, og mere information om operatørerne kan findes på links i nyttig information.

Operatører-af

Lad os starte med hjælpeoperatøren af. Det skaber en observerbar baseret på en simpel værdi.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatør-filter

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Filteroperatøren, som navnet antyder, filtrerer streamsignalet. Hvis operatoren returnerer sand, springer den videre.

Operatører - tag

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

take - Tager værdien af ​​antallet af udsendelser, hvorefter streamen slutter.

Operatører-debounceTime

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

debounceTime - kasserer udsendte værdier, der falder inden for det angivne tidsinterval mellem outputdata - efter tidsintervallet er gået, udsender den sidste værdi.

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

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatører-takeWhile

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Udsender værdier, indtil takeWhile returnerer falsk, og afmelder derefter strømmen.

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

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatører-kombiner Seneste

Den kombinerede operatør combineLatest minder lidt om lover.all. Den kombinerer flere strømme til én. Efter at hver tråd har lavet mindst én udsendelse, får vi de seneste værdier fra hver som et array. Yderligere, efter enhver udledning fra de kombinerede strømme, vil det give nye værdier.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatører-zip

Zip - venter på en værdi fra hver strøm og danner et array baseret på disse værdier. Hvis værdien ikke kommer fra nogen tråd, vil gruppen ikke blive dannet.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatører - forkJoin

forkJoin forbinder også tråde, men det udsender kun en værdi, når alle tråde er færdige.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

const { forkJoin, Observable } = Rx;
const { take } = RxOperators;

const observer_1 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next('a: ' + i++);
  }, 1000);
}).pipe(take(3));

const observer_2 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 750
  setInterval(() => {
    observer.next('b: ' + i++);
  }, 750);
}).pipe(take(5));

const observer_3 = Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 500
  setInterval(() => {
    observer.next('c: ' + i++);
  }, 500);
}).pipe(take(4));

forkJoin(observer_1, observer_2, observer_3);

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatører-kort

Korttransformationsoperatøren omdanner udsendelsesværdien til en ny.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

const {  Observable } = Rx;
const { take, map } = RxOperators;

Observable.create((observer) => {
  let i = 1;
  // Испускаем значение раз в 1000мс
  setInterval(() => {
    observer.next(i++);
  }, 1000);
}).pipe(
  map(x => x * 10),
  take(3)
);

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatører - del, tryk

Tapoperatoren giver dig mulighed for at udføre bivirkninger, det vil sige alle handlinger, der ikke påvirker sekvensen.

Aktionsselskabet kan forvandle en kold strøm til en varm strøm.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatører er færdige. Lad os gå videre til emnet.

Tænker højt

Og så gik jeg for at drikke te. Jeg er træt af disse eksempler 😀

Emnefamilie

Emnefamilien er et glimrende eksempel på varme tråde. Disse klasser er en slags hybrid, der fungerer som observerbare og observerende på samme tid. Da emnet er en hot stream, skal det afmeldes. Hvis vi taler om de vigtigste metoder, så er disse:

  • næste - videregivelse af nye data til strømmen
  • fejl - fejl og trådafslutning
  • komplet - slutningen af ​​tråden
  • abonner - abonner på en stream
  • afmeld - afmeld strømmen
  • asobservable - transformer til en observatør
  • toPromise - forvandles til et løfte

Tildel 4 5 fagtyper.

Tænker højt

Jeg sagde 4 på streamen, men det viste sig, at de tilføjede en mere. Som man siger, lev og lær.

Simpelt emne new Subject()- den enkleste slags fag. Oprettet uden parametre. Overfører de værdier, der først kom efter abonnementet.

Adfærdsemne new BehaviorSubject( defaultData<T> ) - efter min mening den mest almindelige type fag. Indgangen tager standardværdien. Gemmer altid dataene fra det sidste nummer, som overføres ved tilmelding. Denne klasse har også en nyttig værdimetode, der returnerer den aktuelle værdi af strømmen.

Afspil emne new ReplaySubject(bufferSize?: number, windowTime?: number) - Valgfrit kan det tage som det første argument størrelsen af ​​bufferen af ​​værdier, som det vil gemme i sig selv, og den anden gang, hvor vi har brug for ændringer.

asynkront emne new AsyncSubject() - der sker ikke noget ved tilmelding, og værdien vil først blive returneret, når den er fuldført. Kun den sidste værdi af streamen vil blive returneret.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Dokumentationen er tavs om det, og jeg ser det selv for første gang. Hvem ved, hvad han gør, skriv, vil vi tilføje.

Pyha. Nå, vi har overvejet alt, hvad jeg ville fortælle i dag. Håber denne information var nyttig. Du kan læse litteraturlisten på egen hånd under fanen Nyttige oplysninger.

nyttige oplysninger

Kilde: www.habr.com

Tilføj en kommentar