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.
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.
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?
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.
1) Indtil videre er der ikke sket noget. Browserkonsollen er ren, opkaldsstakken er tom.
2) Derefter tilføjes kommandoen console.log('Hi') til opkaldsstakken.
3) Og det er opfyldt
4) Derefter fjernes console.log('Hi') fra opkaldsstakken.
5) Lad os nu gå videre til kommandoen setTimeout(funktion cb1() {... }). Det føjes til opkaldsstakken.
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.
7) Kommandoen setTimeout(funktion cb1() {... }) har fuldført sit arbejde og fjernes fra opkaldsstakken.
8) Kommandoen console.log('Bye') føjes til opkaldsstakken.
9) Kommandoen console.log('Bye') udføres.
10) Kommandoen console.log('Bye') fjernes fra opkaldsstakken.
11) Efter mindst 5000 ms er gået, slutter timeren og sætter cb1-tilbagekaldet i tilbagekaldskøen.
12) Hændelsesløkken tager funktionen cb1 fra tilbagekaldskøen og skubber den ind på opkaldsstakken.
13) cb1-funktionen udføres og tilføjer console.log('cb1') til opkaldsstakken.
14) Kommandoen console.log('cb1') udføres.
15) Kommandoen console.log('cb1') fjernes fra opkaldsstakken.
16) Funktion cb1 fjernes fra opkaldsstakken.
Lad os se på et eksempel i dynamik:
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.
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.
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.
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:
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.
Operatør-filter
Filteroperatøren, som navnet antyder, filtrerer streamsignalet. Hvis operatoren returnerer sand, springer den videre.
Operatører - tag
take - Tager værdien af antallet af udsendelser, hvorefter streamen slutter.
Operatører-debounceTime
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)
);
Operatører-takeWhile
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 )
);
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.
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));
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.
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));
Operatører - forkJoin
forkJoin forbinder også tråde, men det udsender kun en værdi, når alle tråde er færdige.
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);
Operatører-kort
Korttransformationsoperatøren omdanner udsendelsesværdien til en ny.
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)
);
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.
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.