Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Dag Allemaal. Sergey Omelnitsky heeft contact opgenomen. Nog niet zo lang geleden organiseerde ik een stream over reactief programmeren, waarin ik het had over asynchronie in JavaScript. Vandaag wil ik aantekeningen maken over dit materiaal.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Maar voordat we met het hoofdmateriaal beginnen, moeten we een inleidende opmerking maken. Laten we dus beginnen met definities: wat is een stapel en een wachtrij?

Stapel is een collectie waarvan de elementen worden verkregen op een last-in, first-out LIFO-basis

Wachtrij is een verzameling waarvan de elementen worden verkregen op een first-in, first-out FIFO-basis

Oké, laten we doorgaan.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

JavaScript is een single-threaded programmeertaal. Dit betekent dat er slechts één uitvoeringsthread is en één stapel waarop functies in de wachtrij staan ​​voor uitvoering. Daarom kan JavaScript slechts één bewerking tegelijk uitvoeren, terwijl andere bewerkingen op hun beurt op de stapel wachten totdat ze worden aangeroepen.

Bel stapel is een datastructuur die, simpel gezegd, informatie vastlegt over de plaats in het programma waar we ons bevinden. Als we in een functie terechtkomen, duwen we de ingang ervan naar de bovenkant van de stapel. Wanneer we terugkeren van een functie, halen we het bovenste element van de stapel en komen we terug op de plek waar we de functie hebben aangeroepen. Dit is alles wat de stapel kan doen. En nu een uiterst interessante vraag. Hoe werkt asynchronie dan in JavaScript?

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Naast de stapel hebben browsers zelfs een speciale wachtrij voor het werken met de zogenaamde WebAPI. De functies in deze wachtrij worden pas in volgorde uitgevoerd nadat de stapel volledig is leeggemaakt. Pas daarna worden ze uit de wachtrij op de stapel geduwd voor uitvoering. Als er op dit moment minstens één element op de stapel staat, kunnen deze niet aan de stapel worden toegevoegd. Juist hierdoor is het aanroepen van functies via time-out vaak niet precies in de tijd, omdat de functie niet van de wachtrij naar de stapel kan komen terwijl deze vol is.

Laten we naar het volgende voorbeeld kijken en aan de slag gaan met de stapsgewijze implementatie ervan. Laten we ook eens kijken wat er in het systeem gebeurt.

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

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

1) Er gebeurt nog niets. De browserconsole is duidelijk, de call-stack is leeg.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

2) Vervolgens wordt de opdracht console.log('Hi') toegevoegd aan de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

3) En het is vervuld

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

4) Vervolgens wordt console.log('Hi') verwijderd uit de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

5) Ga nu verder met het commando setTimeout(function cb1() {… }). Het wordt toegevoegd aan de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

6) De opdracht setTimeout(function cb1() {… }) wordt uitgevoerd. De browser maakt een timer aan die deel uitmaakt van de Web API. Er wordt een aftelling uitgevoerd.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

7) De opdracht setTimeout(function cb1() {... }) heeft zijn werk voltooid en is verwijderd uit de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

8) De opdracht console.log('Bye') wordt toegevoegd aan de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

9) De opdracht console.log('Bye') wordt uitgevoerd.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

10) De opdracht console.log('Bye') wordt verwijderd uit de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

11) Nadat ten minste 5000 ms zijn verstreken, wordt de timer beëindigd en wordt callback cb1 in de callback-wachtrij geplaatst.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

12) De gebeurtenislus neemt functie cb1 uit de callback-wachtrij en plaatst deze op de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

13) Functie cb1 wordt uitgevoerd en voegt console.log('cb1') toe aan de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

14) Het commando console.log('cb1') wordt uitgevoerd.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

15) De opdracht console.log('cb1') wordt verwijderd uit de call-stack.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

16) Functie cb1 wordt verwijderd uit de call-stack.

Laten we eens kijken naar een voorbeeld in de dynamiek:

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Welnu, we hebben gekeken hoe asynchronie wordt geïmplementeerd in JavaScript. Laten we het nu kort hebben over de evolutie van asynchrone code.

De evolutie van asynchrone code.

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

Asynchrone programmering zoals we die kennen in JavaScript kan alleen door functies worden geïmplementeerd. Ze kunnen net als elke andere variabele aan andere functies worden doorgegeven. Dit is hoe terugroepacties werden geboren. En het is cool, leuk en speels, totdat het verandert in verdriet, melancholie en verdriet. Waarom? Het is makkelijk:

  • Naarmate de complexiteit van de code toeneemt, verandert het project snel in obscure, herhaaldelijk geneste blokken: de “callback hell”.
  • Foutafhandeling kan gemakkelijk over het hoofd worden gezien.
  • U kunt geen expressies retourneren met return.

Met de komst van Promise werd de situatie iets beter.

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

  • Er verschenen belofteketens, die de leesbaarheid van de code verbeterden
  • Er is een aparte methode verschenen om fouten op te sporen
  • De mogelijkheid toegevoegd voor parallelle uitvoering met Promise.all
  • We kunnen geneste asynchronie oplossen met async/await

Maar beloften hebben hun beperkingen. Een belofte kan bijvoorbeeld niet worden ingetrokken zonder te dansen met een tamboerijn, en het belangrijkste is dat het met één waarde werkt.

Welnu, we hebben reactief programmeren soepel benaderd. Moe? Gelukkig kun je thee gaan zetten, erover nadenken en terugkomen om meer te lezen. En ik zal doorgaan.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Reactieve programmering is een programmeerparadigma gericht op datastromen en veranderingsvoortplanting. Laten we eens nader bekijken wat een datastroom is.

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

const eventsArray = [];

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

Laten we ons voorstellen dat we een invoerveld hebben. We maken een array en voor elke keyup van de invoergebeurtenis slaan we de gebeurtenis op in onze array. Tegelijkertijd zou ik willen opmerken dat onze array is gesorteerd op tijd, d.w.z. de index van latere gebeurtenissen is groter dan de index van eerdere. Zo'n array is een vereenvoudigd model van een gegevensstroom, maar is nog geen stroom. Om ervoor te zorgen dat deze array veilig een stream kan worden genoemd, moet deze op de een of andere manier abonnees kunnen informeren dat er nieuwe gegevens in zijn binnengekomen. Zo komen we bij de definitie van stroom.

Data stroom

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

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

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

beek is een array van gegevens, gesorteerd op tijd, die erop kunnen wijzen dat de gegevens zijn gewijzigd. Stel je nu eens voor hoe handig het wordt om code te schrijven waarbij één actie meerdere gebeurtenissen in verschillende delen van de code vereist. We abonneren ons eenvoudigweg op de stream en deze zal ons op de hoogte stellen wanneer er wijzigingen optreden. En de RxJs-bibliotheek kan dit doen.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

RxJS is een bibliotheek voor het werken met asynchrone en op gebeurtenissen gebaseerde programma's met behulp van waarneembare reeksen. De bibliotheek biedt een basistype Waarneembaar, verschillende hulptypen (Waarnemer, planners, onderwerpen) en operators voor het werken met gebeurtenissen zoals met verzamelingen (in kaart brengen, filteren, verkleinen, alles en soortgelijke van JavaScript Array).

Laten we de basisconcepten van deze bibliotheek begrijpen.

Waarneembaar, waarnemer, producent

Waarneembaar is het eerste basistype waar we naar zullen kijken. Deze klasse bevat het grootste deel van de RxJs-implementatie. Het is gekoppeld aan een waarneembare stream, waarop kan worden geabonneerd met behulp van de abonneermethode.

Observable implementeert een helpermechanisme voor het maken van updates, het zogenaamde Waarnemer. De bron van waarden voor de waarnemer wordt genoemd Producent. Dit kan een array, iterator, websocket, een soort gebeurtenis, enz. zijn. We kunnen dus zeggen dat waarneembaar een dirigent is tussen Producent en Waarnemer.

Observable verwerkt drie soorten Observer-gebeurtenissen:

  • volgende – nieuwe gegevens
  • fout – een fout als de reeks is beëindigd vanwege een uitzondering. deze gebeurtenis impliceert ook de voltooiing van de reeks.
  • compleet — signaal over de voltooiing van de reeks. Dit betekent dat er geen nieuwe gegevens meer zullen zijn.

Laten we de demo bekijken:

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

In het begin verwerken we de waarden 1, 2, 3 en na 1 seconde. we krijgen er 4 en beëindigen onze stream.

Hardop denken

En toen besefte ik dat het vertellen ervan interessanter was dan erover schrijven. 😀

Abonnement

Wanneer we ons abonneren op een stream, maken we een nieuwe klas aan abonnementwaarmee we ons via deze methode kunnen afmelden Afmelden. Met deze methode kunnen we ook abonnementen groeperen toevoegen. Welnu, het is logisch dat we de threads kunnen degroeperen met behulp van verwijderen. De methoden voor toevoegen en verwijderen accepteren een ander abonnement als invoer. Ik wil er rekening mee houden dat wanneer we ons afmelden, we ons afmelden voor alle onderliggende abonnementen alsof ze de afmeldmethode hadden genoemd. Doe Maar.

Soorten stromen

Populair
VERKOUDHEID

Producent wordt buiten het waarneembare gecreëerd
Producent wordt binnen waarneembaar gecreëerd

Gegevens worden overgedragen op het moment dat het waarneembare wordt gecreëerd
Gegevens worden verstrekt op het moment van inschrijving

Er is extra logica nodig voor het afmelden
De draad eindigt vanzelf

Maakt gebruik van een één-op-veel-relatie
Maakt gebruik van een één-op-één-relatie

Alle abonnementen hebben dezelfde betekenis
Abonnementen zijn onafhankelijk

Als u geen abonnement heeft, kunnen gegevens verloren gaan
Geeft alle streamwaarden opnieuw uit voor een nieuw abonnement

Om een ​​analogie te geven: ik zou een hete stroom beschouwen als een film in een theater. Op welk tijdstip je arriveerde, vanaf dat moment begon je te kijken. Ik zou een koude stroom vergelijken met een technische oproep. steun. Elke beller luistert van begin tot eind naar de voicemailopname, maar u kunt ophangen door u af te melden.

Ik zou willen opmerken dat er ook zogenaamde warme stromen zijn (ik ben deze definitie uiterst zelden tegengekomen en alleen in buitenlandse gemeenschappen) - dit is een stroom die transformeert van een koude stroom naar een warme stroom. De vraag rijst - waar te gebruiken)) Ik zal een voorbeeld uit de praktijk geven.

Ik werk met Angular. Hij maakt actief gebruik van rxjs. Om gegevens naar de server te ontvangen, verwacht ik een koude thread en gebruik ik deze thread in de sjabloon met behulp van asyncPipe. Als ik deze pijp meerdere keren gebruik, zal elke pijp, terugkerend naar de definitie van een koude stroom, gegevens van de server opvragen, wat op zijn zachtst gezegd vreemd is. En als ik een koude stroom in een warme stroom omzet, zal het verzoek één keer gebeuren.

Over het algemeen is het begrijpen van het soort stromen vrij moeilijk voor beginners, maar wel belangrijk.

Operators

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

Operators bieden ons de mogelijkheid om onze mogelijkheden om met streams te werken uit te breiden. Ze helpen bij het beheersen van de gebeurtenissen die plaatsvinden in het waarneembare. We zullen een paar van de meest populaire bekijken, en meer details over de operators zijn te vinden via de links in de nuttige informatie.

Exploitanten - van

Laten we beginnen met de hulpoperator van. Het creëert een waarneembaar resultaat op basis van een eenvoudige waarde.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Operatoren - filteren

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

De filteroperator filtert, zoals de naam al doet vermoeden, het streamsignaal. Als de operator waar retourneert, slaat hij verder over.

Exploitanten - neem

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

take — Neemt de waarde van het aantal emitters, waarna de thread eindigt.

Operators - debounceTime

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

debounceTime - verwijdert uitgezonden waarden die binnen het opgegeven tijdsinterval tussen uitvoer vallen - nadat het tijdsinterval is verstreken, zendt de laatste waarde uit.

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

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Operators - takeWhile

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Zendt waarden uit totdat takeWhile false retourneert, waarna het zich afmeldt voor de thread.

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

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Operators - combineLatest

De operator combineLatest lijkt enigszins op belofte.all. Het combineert meerdere threads in één. Nadat elke thread minstens één emissie heeft gemaakt, krijgen we van elke thread de nieuwste waarden in de vorm van een array. Verder zal het na elke emissie uit de samengevoegde stromen nieuwe waarden opleveren.

Asynchrone programmering in 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));

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Exploitanten - zip

Zip - Wacht op een waarde van elke thread en vormt een array op basis van deze waarden. Als de waarde uit geen enkele thread komt, wordt de groep niet gevormd.

Asynchrone programmering in 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));

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Operators - forkJoin

forkJoin voegt ook threads samen, maar geeft alleen een waarde af als alle threads voltooid zijn.

Asynchrone programmering in 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);

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Exploitanten - kaart

De kaarttransformatie-operator transformeert de emitterwaarde in een nieuwe.

Asynchrone programmering in 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)
);

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

Operators – delen, tikken

Met de tapoperator kunt u bijwerkingen uitvoeren, dat wil zeggen alle acties die de reeks niet beïnvloeden.

De exploitant van een gedeeld nutsbedrijf kan een koude stroom in een warme stroom veranderen.

Asynchrone programmering in JavaScript (Callback, Promise, RxJs)

We zijn klaar met de operators. Laten we verder gaan met Onderwerp.

Hardop denken

En toen ging ik thee drinken. Ik ben deze voorbeelden beu 😀

Onderwerp familie

De onderwerpfamilie is een goed voorbeeld van hete stromen. Deze klassen zijn een soort hybride die tegelijkertijd als waarneembaar en waarnemer fungeren. Omdat het onderwerp een hot topic is, is het noodzakelijk om je hiervoor af te melden. Als we het hebben over de belangrijkste methoden, dan zijn dit:

  • volgende – overdracht van nieuwe gegevens naar de stream
  • fout – fout en beëindiging van de thread
  • voltooid – voltooiing van de thread
  • abonneren – abonneer je op een stream
  • afmelden – afmelden voor de stream
  • asObservable – transformeer in een waarnemer
  • toPromise – verandert in een belofte

Er zijn 4 5 soorten onderwerpen.

Hardop denken

Er waren vier mensen aan het praten op de stream, maar het bleek dat ze er nog een hadden toegevoegd. Zoals ze zeggen, leef en leer.

Eenvoudig onderwerp new Subject()– het eenvoudigste type onderwerpen. Gemaakt zonder parameters. Verzendt waarden die alleen worden ontvangen na abonnement.

GedragOnderwerp new BehaviorSubject( defaultData<T> ) – naar mijn mening het meest voorkomende type onderwerp. De invoer heeft de standaardwaarde. Slaat altijd de gegevens van het laatste nummer op, die worden verzonden bij het abonneren. Deze klasse heeft ook een nuttige waardemethode, die de huidige waarde van de stream retourneert.

Opnieuw afspelenOnderwerp new ReplaySubject(bufferSize?: number, windowTime?: number) — De invoer kan optioneel als eerste argument de grootte aannemen van de waardenbuffer die hij in zichzelf zal opslaan, en als tweede de tijd gedurende welke we veranderingen nodig hebben.

Asynchroononderwerp new AsyncSubject() — er gebeurt niets als u zich abonneert, en de waarde wordt pas geretourneerd als deze compleet is. Alleen de laatste waarde van de stream wordt geretourneerd.

WebSocketOnderwerp new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — De documentatie zwijgt over hem en ik zie hem voor de eerste keer. Als iemand weet wat hij doet, schrijf het dan, dan voegen we het toe.

Opluchting. Nou, we hebben alles besproken wat ik je vandaag wilde vertellen. Ik hoop dat deze informatie nuttig was. De lijst met referenties kunt u zelf nalezen op het tabblad nuttige informatie.

nuttige informatie

Bron: www.habr.com

Voeg een reactie