Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Bok svima. U kontaktu Omelnitsky Sergey. Ne tako davno, vodio sam stream o reaktivnom programiranju, gdje sam govorio o asinkroniji u JavaScriptu. Danas bih želio sažeti ovaj materijal.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Ali prije nego počnemo s glavnim materijalom, moramo napraviti uvod. Pa počnimo s definicijama: što su stog i red?

Stog je kolekcija čiji se elementi dohvaćaju prema LIFO principu "zadnji ušao, prvi izašao".

Red je zbirka čiji se elementi dobivaju po principu (“prvi ušao, prvi izašao” FIFO

U redu, nastavimo.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

JavaScript je jednonitni programski jezik. To znači da ima samo jednu nit izvršavanja i jedan stog gdje su funkcije u redu čekanja za izvršenje. Stoga JavaScript može izvoditi samo jednu operaciju u isto vrijeme, dok će druge operacije čekati svoj red na stogu dok se ne pozovu.

Stog poziva je podatkovna struktura koja, jednostavno rečeno, bilježi podatke o mjestu u programu na kojem se nalazimo. Ako skočimo u funkciju, guramo njen unos na vrh stoga. Kada se vratimo iz funkcije, izbacimo najviši element sa stoga i završimo tamo odakle smo pozvali ovu funkciju. To je sve što hrpa može. A sad jedno vrlo zanimljivo pitanje. Kako onda asinkronija radi u JavasScriptu?

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Zapravo, osim hrpe, preglednici imaju poseban red čekanja za rad s takozvanim WebAPI-jem. Funkcije iz ovog reda čekanja izvršavat će se redom tek nakon što se stog potpuno očisti. Tek nakon toga se iz reda čekanja stavljaju na stog za izvršenje. Ako postoji barem jedan element na stogu u ovom trenutku, onda oni ne mogu doći na stog. Upravo zbog toga, pozivanje funkcija prema isteku vremena često je vremenski netočno, jer funkcija ne može doći iz reda čekanja na stog dok je puna.

Pogledajmo sljedeći primjer i prođimo kroz njega korak po korak. Pogledajmo i što se događa u sustavu.

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

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

1) Za sada se ništa ne događa. Konzola preglednika je čista, skup poziva prazan.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

2) Zatim se naredba console.log('Hi') dodaje u skup poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

3) I ispunjeno je

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

4) Zatim se console.log('Hi') uklanja iz poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

5) Sada prijeđimo na naredbu setTimeout(function cb1() {… }). Dodaje se u skup poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

6) Izvršava se naredba setTimeout(function cb1() {… }). Preglednik stvara mjerač vremena koji je dio Web API-ja. Izvršit će odbrojavanje.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

7) Naredba setTimeout(function cb1() {… }) je završila svoj posao i uklonjena je iz poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

8) Naredba console.log('Bye') dodaje se u skup poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

9) Naredba console.log('Bye') je izvršena.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

10) Naredba console.log('Bye') je uklonjena iz poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

11) Nakon što je prošlo najmanje 5000 ms, mjerač vremena završava i stavlja cb1 povratni poziv u red čekanja za povratni poziv.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

12) Petlja događaja preuzima funkciju cb1 iz reda povratnih poziva i gura je na stog poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

13) Funkcija cb1 se izvršava i dodaje console.log('cb1') u stog poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

14) Izvršena je naredba console.log('cb1').

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

15) Naredba console.log('cb1') je uklonjena iz skupa poziva.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

16) Funkcija cb1 je uklonjena iz poziva.

Pogledajmo primjer u dinamici:

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Pa, pogledali smo kako je asinkronija implementirana u JavaScriptu. Razgovarajmo sada ukratko o evoluciji asinkronog koda.

Evolucija asinkronog koda.

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

Asinkrono programiranje kakvo poznajemo u JavaScriptu može se izvesti samo s funkcijama. Mogu se proslijediti drugim funkcijama kao i svaka druga varijabla. Tako su rođeni povratni pozivi. I cool je, zabavno i žarko, sve dok ne preraste u tugu, melankoliju i tugu. Zašto? Da, jednostavno je:

  • Kako složenost koda raste, projekt se brzo pretvara u nejasne višestruke ugniježđene blokove - "pakao povratnog poziva".
  • Rješavanje pogrešaka može se lako previdjeti.
  • Ne možete vratiti izraze s return.

Dolaskom Promisea situacija je postala malo bolja.

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

  • Pojavili su se lanci obećanja, što je poboljšalo čitljivost koda
  • Postojala je posebna metoda presretanja pogrešaka
  • Paralelno izvođenje s Promise.all dodano
  • Ugniježđenu asinkroniju možemo riješiti s async/await

Ali obećanje ima svoja ograničenja. Na primjer, obećanje, bez plesa s tamburinom, ne može se poništiti, a što je najvažnije, radi s jednom vrijednošću.

Pa, ovdje se glatko približavamo reaktivnom programiranju. Umoran? Pa, dobra stvar je što možete otići skuhati galebove, razmisliti i vratiti se čitati više. I nastavit ću.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Reaktivno programiranje - programska paradigma usmjerena na tokove podataka i širenje promjena. Pogledajmo pobliže što je tok podataka.

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

const eventsArray = [];

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

Zamislimo da imamo polje za unos. Stvaramo niz i za svaki unos tipke ulaznog događaja pohranit ćemo događaj u naš niz. U isto vrijeme, želio bih napomenuti da je naš niz razvrstan po vremenu, tj. indeks kasnijih događaja veći je od indeksa ranijih događaja. Takav niz je pojednostavljeni model toka podataka, ali još nije tok. Da bi se ovaj niz sa sigurnošću mogao nazvati streamom, on mora moći na neki način obavijestiti pretplatnike da su u njega stigli novi podaci. Tako dolazimo do definicije toka.

Tok podataka

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

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

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

potok je niz podataka poredanih po vremenu koji može značiti da su se podaci promijenili. Zamislite sada kako postaje zgodno pisati kod u kojem trebate pokrenuti nekoliko događaja u različitim dijelovima koda za jednu radnju. Jednostavno se pretplatimo na stream i on će nam javiti kada dođe do promjena. I biblioteka RxJs to može učiniti.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

RxJS je knjižnica za rad s asinkronim programima i programima temeljenim na događajima koji koriste vidljive sekvence. Knjižnica pruža glavni tip primjetan, nekoliko vrsta pomoćnika (Promatrači, planeri, subjekti) i operatori za rad s događajima kao i s zbirkama (mapirati, filtrirati, smanjiti, svaki i slični iz JavaScript Array).

Hajdemo razumjeti osnovne koncepte ove knjižnice.

Uočljiv, promatrač, proizvođač

Observable je prvi osnovni tip koji ćemo pogledati. Ova klasa sadrži glavni dio RxJs implementacije. Povezan je s vidljivim tokom, na koji se možete pretplatiti metodom pretplate.

Observable implementira pomoćni mehanizam za kreiranje ažuriranja, tzv Posmatrač. Izvor vrijednosti za promatrača se zove Producent. To može biti niz, iterator, web socket, neka vrsta događaja itd. Dakle, možemo reći da je observable dirigent između proizvođača i promatrača.

Observable obrađuje tri vrste Observer događaja:

  • sljedeći - novi podaci
  • pogreška - pogreška ako je niz prekinut zbog iznimke. ovaj događaj također implicira kraj niza.
  • kompletan - signal o kraju niza. To znači da više neće biti novih podataka

Pogledajmo demo:

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Na početku ćemo obraditi vrijednosti 1, 2, 3, a nakon 1 sek. dobijemo 4 i završimo našu nit.

Razmišljajući naglas

I tada sam shvatio da je zanimljivije pričati nego pisati o tome. 😀

Pretplata

Kada se pretplatimo na stream, stvaramo novu klasu pretplata, što nam daje mogućnost da odjavimo pretplatu metodom unsubscribe. Također možemo grupirati pretplate korištenjem metode dodati. Pa, logično je da možemo razgrupirati niti pomoću ukloniti. Metode dodavanja i uklanjanja prihvaćaju različite pretplate kao unos. Želio bih napomenuti da kada odjavljujemo pretplatu, odjavljujemo sve podređene pretplate kao da su također pozvali metodu odjave. Samo naprijed.

Vrste potoka

vRUĆE
HLADNO

Proizvođač je stvoren izvan vidljivog
Proizvođač je stvoren unutar vidljivog

Podaci se prosljeđuju u trenutku kada je observable kreiran
Podaci se daju u trenutku pretplate.

Treba više logike za odjavu
Nit se prekida sama od sebe

Koristi odnos jedan prema više
Koristi odnos jedan-na-jedan

Sve pretplate imaju istu vrijednost
Pretplate su neovisne

Podaci se mogu izgubiti ako nema pretplate
Ponovno izdaje sve vrijednosti streama za novu pretplatu

Da dam analogiju, zamislio bih vrući tok poput filma u kinu. U kojem ste trenutku došli, od tog trenutka ste počeli gledati. Hladni potok usporedio bih sa zovom u njima. podrška. Svaki pozivatelj sluša snimku telefonske sekretarice od početka do kraja, ali možete prekinuti vezu uz odjavu.

Napominjem da postoje i takozvani topli tokovi (takvu definiciju sam susreo izuzetno rijetko i to samo u stranim zajednicama) - to je tok koji se pretvara iz hladnog toka u vrući. Postavlja se pitanje - gdje koristiti)) Dat ću primjer iz prakse.

Radim s Angularom. Aktivno koristi rxjs. Za prijenos podataka na poslužitelj očekujem hladni tok i koristim ovaj tok u predlošku koristeći asyncPipe. Ako ovu cijev koristim nekoliko puta, tada će, vraćajući se na definiciju hladnog toka, svaka cijev tražiti podatke od poslužitelja, što je u najmanju ruku čudno. A ako pretvorim hladni tok u topli, tada će se zahtjev dogoditi jednom.

Općenito, razumijevanje vrste tokova prilično je teško za početnike, ali važno.

Operateri

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

Operateri nam daju mogućnost rada sa streamovima. Oni pomažu kontrolirati događaje koji teku u Observable. Razmotrit ćemo nekoliko najpopularnijih, a više informacija o operaterima možete pronaći na poveznicama u korisnim informacijama.

Operateri-od

Počnimo s pomoćnim operatorom of. Stvara Observable na temelju jednostavne vrijednosti.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operatori-filtar

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operator filtera, kao što ime sugerira, filtrira signal toka. Ako operator vrati true, tada se preskače dalje.

Operateri - uzeti

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

take - Uzima vrijednost broja emitiranja, nakon čega se stream završava.

Operatori-debounceTime

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

debounceTime - odbacuje emitirane vrijednosti koje spadaju unutar navedenog vremenskog intervala između izlaznih podataka - nakon što vremenski interval prođe, emitira posljednju vrijednost.

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

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operatori-takeWhile

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Emitira vrijednosti dok takeWhile ne vrati false, a zatim se odjavljuje s niti.

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

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operatori-kombajnNajnovije

Kombinirani operator combineLatest donekle je sličan promise.all. Kombinira više tokova u jedan. Nakon što svaka nit napravi barem jedno emitiranje, dobivamo najnovije vrijednosti od svake kao niz. Nadalje, nakon bilo kakvog emitiranja iz kombiniranih tokova, to će dati nove vrijednosti.

Asinkrono programiranje u JavaScriptu (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));

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operatori-zip

Zip - čeka vrijednost iz svakog toka i formira niz na temelju tih vrijednosti. Ako vrijednost ne dolazi ni iz jedne niti, tada se grupa neće formirati.

Asinkrono programiranje u JavaScriptu (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));

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operatori - forkJoin

forkJoin također spaja niti, ali emitira vrijednost samo kada su sve niti dovršene.

Asinkrono programiranje u JavaScriptu (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);

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operatori-mapa

Operator transformacije karte transformira emitiranu vrijednost u novu.

Asinkrono programiranje u JavaScriptu (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)
);

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operatori - dijeljenje, dodir

Operator slavine vam omogućuje da radite nuspojave, odnosno sve radnje koje ne utječu na slijed.

Operater dijeljenja komunalnih usluga može pretvoriti hladni tok u vrući tok.

Asinkrono programiranje u JavaScriptu (Callback, Promise, RxJs)

Operatori su gotovi. Prijeđimo na Predmet.

Razmišljajući naglas

A onda sam otišao popiti čaj. Umoran sam od ovih primjera 😀

Obitelj predmeta

Predmetna obitelj najbolji je primjer vrućih niti. Ove klase su svojevrsni hibridi koji djeluju kao vidljivi i promatrači u isto vrijeme. Budući da je tema vrući stream, morate se odjaviti s nje. Ako govorimo o glavnim metodama, onda su to:

  • sljedeći - prosljeđivanje novih podataka u tok
  • error - greška i prekid niti
  • dovršeno - kraj konca
  • subscribe - pretplatite se na stream
  • unsubscribe - odjava pretplate na stream
  • asObservable - transformirati se u promatrača
  • toPromise - pretvara se u obećanje

Dodijelite 4 5 vrsta predmeta.

Razmišljajući naglas

Rekao sam 4 na streamu, ali ispalo je da su dodali još jednog. Kako se kaže, živi i uči.

Jednostavan predmet new Subject()- najjednostavnija vrsta predmeta. Stvoreno bez parametara. Prolazi vrijednosti koje su došle tek nakon pretplate.

BehaviorSubject new BehaviorSubject( defaultData<T> ) - po mom mišljenju najčešći tip subjekta. Unos ima zadanu vrijednost. Uvijek sprema podatke zadnjeg broja, koji se prenose prilikom pretplate. Ova klasa također ima korisnu metodu vrijednosti koja vraća trenutnu vrijednost toka.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Po želji, kao prvi argument može uzeti veličinu međuspremnika vrijednosti koje će pohraniti u sebe, a drugi vrijeme tijekom kojeg su nam potrebne promjene.

asinkroni subjekt new AsyncSubject() - ništa se ne događa prilikom pretplate, a vrijednost će biti vraćena tek kada se završi. Vratit će se samo zadnja vrijednost toka.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Dokumentacija o tome šuti i ja to prvi put vidim. Tko zna što radi, napišite, mi ćemo dodati.

Fuj. Pa, razmotrili smo sve što sam danas htio reći. Nadam se da su ove informacije bile korisne. Popis literature možete sami pročitati u kartici Korisne informacije.

korisne informacije

Izvor: www.habr.com

Dodajte komentar