Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Hej alla. Sergey Omelnitsky är i kontakt. För inte så länge sedan var jag värd för en stream om reaktiv programmering, där jag pratade om asynkron i JavaScript. Idag skulle jag vilja göra anteckningar om detta material.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Men innan vi börjar med huvudmaterialet måste vi göra en inledande anteckning. Så låt oss börja med definitioner: vad är en stack och en kö?

Stack är en samling vars element erhålls på en sist in, först ut LIFO-basis

vända är en samling vars element erhålls på en först in, först ut FIFO-basis

Okej, låt oss fortsätta.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

JavaScript är ett entrådigt programmeringsspråk. Det betyder att det bara finns en exekveringstråd och en stack på vilka funktioner är köade för exekvering. Därför kan JavaScript bara utföra en operation åt gången, medan andra operationer väntar på sin tur på stacken tills de anropas.

Ring stack är en datastruktur som enkelt uttryckt registrerar information om platsen i programmet där vi befinner oss. Om vi ​​går in i en funktion trycker vi dess ingång till toppen av stapeln. När vi återvänder från en funktion, poppar vi det översta elementet från stacken och hamnar tillbaka där vi anropade funktionen. Detta är allt som stacken kan göra. Och nu en mycket intressant fråga. Hur fungerar då asynkron i JavasScript?

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Faktum är att, förutom stacken, har webbläsare en speciell kö för att arbeta med den så kallade WebAPI. Funktionerna i den här kön kommer att köras i ordning först efter att stacken har tömts helt. Först efter detta skjuts de från kön till stacken för utförande. Om det finns minst ett element i stacken för tillfället kan de inte läggas till i stacken. Det är just därför som att anropa funktioner via timeout ofta inte är exakt i tiden, eftersom funktionen inte kan ta sig från kön till stacken medan den är full.

Låt oss titta på följande exempel och komma igång med dess implementering steg för steg. Låt oss också se 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) Inget händer ännu. Webbläsarkonsolen är ren, samtalsstacken är tom.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

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

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

3) Och det är uppfyllt

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

4) Sedan tas console.log('Hi') bort från samtalsstacken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

5) Gå nu vidare till kommandot setTimeout(funktion cb1() {... }). Den läggs till samtalsstacken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

6) Kommandot setTimeout(funktion cb1() {... }) exekveras. Webbläsaren skapar en timer som är en del av Web API. Det kommer att utföra en nedräkning.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

7) Kommandot setTimeout(function cb1() {... }) har slutfört sitt arbete 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') kö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) Efter att minst 5000 ms har gått avslutas timern och placerar callback cb1 i callback-kön.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

12) Händelseloopen tar funktionen cb1 från återuppringningskön och placerar den på anropsstacken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

13) Funktion cb1 exekveras och lägger till console.log('cb1') till anropsstacken.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

14) Kommandot console.log('cb1') kö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) Funktion cb1 tas bort från anropsstacken.

Låt oss titta på ett exempel inom dynamik:

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Tja, vi tittade på hur asynkroni implementeras i JavaScript. Låt oss nu prata kort om utvecklingen av asynkron kod.

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

Asynkron programmering som vi känner den i JavaScript kan endast implementeras av funktioner. De kan skickas som vilken annan variabel som helst till andra funktioner. Det var så callbacks föddes. Och det är coolt, roligt och lekfullt, tills det förvandlas till sorg, melankoli och sorg. Varför? Det är enkelt:

  • När kodens komplexitet ökar förvandlas projektet snabbt till obskyra, upprepade kapslade block - "callback hell".
  • Felhantering kan vara lätt att missa.
  • Du kan inte returnera uttryck med retur.

I och med Promises tillkomst blev situationen lite 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);
});

  • Löfteskedjor dök upp, vilket förbättrade kodläsbarheten
  • En separat metod för att fånga fel har dykt upp
  • Lade till möjligheten till parallell exekvering med Promise.all
  • Vi kan lösa kapslad asynkroni med async/await

Men löften har sina begränsningar. Till exempel kan ett löfte inte annulleras utan att dansa med en tamburin, och det viktigaste är att det fungerar med ett värde.

Tja, vi har smidigt närmat oss reaktiv programmering. Trött? Tja, lyckligtvis kan du gå och göra lite te, tänka på det och återkomma för att läsa mer. Och jag kommer att fortsätta.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Reaktiv programmering är ett programmeringsparadigm fokuserat på dataflöden och förändringsutbredning. Låt oss titta närmare på vad en dataström är.

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

const eventsArray = [];

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

Låt oss föreställa oss att vi har ett inmatningsfält. Vi skapar en array och för varje knapptryckning av inmatningshändelsen kommer vi att lagra händelsen i vår array. Samtidigt vill jag notera att vår array är sorterad efter tid, d.v.s. indexet för senare händelser är större än indexet för tidigare. En sådan array är en förenklad modell av ett dataflöde, men det är ännu inte ett flöde. För att denna matris säkert ska kunna kallas en ström, måste den på något sätt kunna informera abonnenter om att ny data har kommit in i den. Därmed kommer vi till definitionen av flöde.

Dataström

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

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

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

ström är en rad data sorterade efter tid som kan indikera att data har ändrats. Föreställ dig nu hur bekvämt det blir att skriva kod där en åtgärd kräver att flera händelser anropas i olika delar av koden. Vi prenumererar helt enkelt på strömmen och den kommer att meddela oss när ändringar sker. Och RxJs-biblioteket kan göra detta.

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 en grundläggande typ Märkbar, flera hjälptyper (Observatör, Schemaläggare, Ämnen) och operatörer för att arbeta med evenemang som med samlingar (kartlägga, filtrera, minska, varje och liknande från JavaScript Array).

Låt oss förstå de grundläggande begreppen i detta bibliotek.

Observerbar, observatör, producent

Observerbar är den första grundläggande typen vi ska titta på. Den här klassen innehåller huvuddelen av RxJs implementering. Den är associerad med en observerbar ström, som kan prenumereras på med hjälp av prenumerationsmetoden.

Observable implementerar en hjälpmekanism för att skapa uppdateringar, den sk Observer. Värdekällan för Observer kallas Producent. Detta kan vara en array, iterator, webbsocket, någon form av händelse, etc. Så vi kan säga att observable är en ledare mellan producent och observatör.

Observable hanterar tre typer av observatörshändelser:

  • nästa – nya data
  • fel – ett fel om sekvensen avslutades på grund av ett undantag. denna händelse innebär också att sekvensen är slutförd.
  • komplett — signal om slutförandet av sekvensen. Det betyder att det inte kommer fler nya uppgifter.

Låt oss se demon:

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

I början kommer vi att bearbeta värdena 1, 2, 3 och efter 1 sekund. vi får 4 och avslutar vår stream.

Tänker högt

Och då insåg jag att det var mer intressant att berätta än att skriva om det. 😀

Prenumeration

När vi prenumererar på en stream skapar vi en ny klass prenumerationvilket ger oss möjlighet att avregistrera oss med metoden prenumerationen. Vi kan också gruppera abonnemang med metoden lägga till. Tja, det är logiskt att vi kan avgruppera trådarna med hjälp av ta bort. Lägg till och ta bort metoder accepterar en annan prenumeration som indata. Jag vill notera att när vi avslutar prenumerationen så avslutar vi prenumerationen från alla underordnade prenumerationer som om de hade anropat avregistreringsmetoden. Varsågod.

Typer av strömmar

HET
KALL

Producenten skapas utanför observerbar
Producenten skapas inuti observerbar

Data överförs vid den tidpunkt då det observerbara skapas
Data tillhandahålls vid tidpunkten för prenumerationen

Behöver ytterligare logik för att avsluta prenumerationen
Tråden avslutas av sig själv

Använder en en-till-många-relation
Använder en en-till-en relation

Alla abonnemang har samma innebörd
Prenumerationer är oberoende

Data kan gå förlorade om du inte har ett abonnemang
Återutser alla streamvärden för en ny prenumeration

För att ge en analogi, skulle jag tänka på en het stream som en film på en teater. Vid vilken tidpunkt du anlände, från det ögonblicket började du titta. Jag skulle jämföra ett kallt flöde med ett samtal inom teknik. Stöd. Alla som ringer lyssnar på röstmeddelandeinspelningen från början till slut, men du kan lägga på genom att avsluta prenumerationen.

Jag skulle vilja notera att det också finns så kallade varma flöden (jag har stött på denna definition extremt sällan och bara i främmande samhällen) - det här är ett flöde som övergår från ett kallt flöde till ett varmt. Frågan uppstår - var man ska använda)) Jag ska ge ett exempel från praktiken.

Jag jobbar med Angular. Han använder aktivt rxjs. För att ta emot data till servern förväntar jag mig en kall tråd och använder denna tråd i mallen med asyncPipe. Om jag använder det här röret flera gånger, då återgår till definitionen av en kall ström, kommer varje rör att begära data från servern, vilket är minst sagt konstigt. Och om jag omvandlar en kall ström till en varm, kommer begäran att ske en gång.

I allmänhet är det ganska svårt för nybörjare att förstå typen av flöden, men viktigt.

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 ger oss möjligheten att utöka vår förmåga att arbeta med strömmar. De hjälper till att kontrollera händelserna som inträffar i Observable. Vi kommer att titta på ett par av de mest populära, och mer information om operatörerna kan hittas med hjälp av länkarna i den användbara informationen.

Operatörer - av

Låt oss börja med hjälpoperatören för. Det skapar en observerbar baserat på ett enkelt värde.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Operatörer - filter

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Filteroperatören, som namnet antyder, filtrerar strömsignalen. Om operatören returnerar sant hoppar den vidare.

Operatörer - ta

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

take — Tar värdet av antalet sändare, varefter tråden slutar.

Operatörer - debounceTime

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

debounceTime - kastar utsända värden som faller inom det angivna tidsintervallet mellan utgångar - efter att tidsintervallet har passerat, avger det sista värdet.

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)

Avger värden tills takeWhile returnerar false, varefter den avregistrerar sig från tråden.

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

CombinLatest-operatören liknar något som lovar.all. Den kombinerar flera trådar till en. Efter att varje tråd gör minst en emission får vi de senaste värdena från varje i form av en array. Vidare, efter eventuella utsläpp från de sammanslagna strömmarna, kommer det 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;
  // Испускаем значение раз в 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 - Väntar på ett värde från varje tråd och bildar en array baserat på dessa värden. Om värdet inte kommer från någon tråd 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;
  // Испускаем значение раз в 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 sammanfogar också trådar, men det avger ett värde bara när alla trådar är färdiga.

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

Karttransformationsoperatören omvandlar emittervärdet till ett nytt.

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 – dela, tryck

Kranoperatören låter dig göra biverkningar, det vill säga alla åtgärder som inte påverkar sekvensen.

Aktieoperatören kan förvandla en kall ström till en varm.

Asynkron programmering i JavaScript (Callback, Promise, RxJs)

Vi är klara med operatörerna. Låt oss gå vidare till Ämne.

Tänker högt

Och så gick jag och drack lite te. Jag är trött på de här exemplen 😀

Ämnesfamilj

Ämnesfamiljen är ett utmärkt exempel på heta flöden. Dessa klasser är en sorts hybrid som fungerar samtidigt som observerbar och observatör. Eftersom ämnet är en het tråd är det nödvändigt att avregistrera sig från det. Om vi ​​pratar om de viktigaste metoderna är dessa:

  • nästa – överföring av ny data till strömmen
  • fel – fel och trådavslutning
  • komplett – färdigställande av tråden
  • prenumerera – prenumerera på en stream
  • avregistrera – avregistrera dig från strömmen
  • asObservable – förvandlas till en observatör
  • toPromise – förvandlas till ett löfte

Det finns 4 5 typer av ämnen.

Tänker högt

Det var 4 personer som pratade på streamen, men det visade sig att de lade till en till. Som de säger, lev och lär.

Enkelt ämne new Subject()– den enklaste typen av ämnen. Skapat utan parametrar. Överför värden som tas emot först efter prenumeration.

Beteende Ämne new BehaviorSubject( defaultData<T> ) – enligt mig den vanligaste typen av ämne. Ingången tar standardvärdet. Sparar alltid data från det senaste numret, som överförs vid prenumeration. Den här klassen har också en användbar värdemetod, som returnerar strömmens nuvarande värde.

Spela om ämne new ReplaySubject(bufferSize?: number, windowTime?: number) — Inmatningen kan valfritt ta som första argument storleken på bufferten av värden som den kommer att lagra i sig själv, och som den andra tiden under vilken vi behöver ändringar.

AsyncSubject new AsyncSubject() — ingenting händer när du prenumererar, och värdet kommer att returneras först när det är klart. Endast det sista värdet av strömmen kommer att returneras.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — Dokumentationen är tyst om honom och jag ser honom för första gången. Om någon vet vad han gör, skriv gärna så lägger vi till det.

Puh. Tja, vi har täckt allt jag ville berätta för dig idag. Jag hoppas att denna information var användbar. Du kan själv läsa referenslistan på fliken användbar information.

användbar information

Källa: will.com

Lägg en kommentar