Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

Zdravo svima. U kontaktu Omelnitsky Sergey. Ne tako davno, vodio sam stream o reaktivnom programiranju, gdje sam govorio o asinhroniji u JavaScriptu. Danas bih želio rezimirati ovaj materijal.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

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

Stack je kolekcija čiji se elementi preuzimaju po principu LIFO „posljednji ušao, prvi izašao“.

Red čekanja je kolekcija čiji se elementi dobijaju po principu (“prvi ušao, prvi izašao” FIFO

Ok, nastavimo.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

JavaScript je jednonitni programski jezik. To znači da ima samo jednu nit izvršenja i jedan stek gdje su funkcije stavljene u red za izvršenje. Stoga JavaScript može izvoditi samo jednu operaciju istovremeno, dok će druge operacije čekati svoj red na steku dok se ne pozovu.

Stack poziva je struktura podataka koja, jednostavno rečeno, bilježi informacije o mjestu u programu na kojem se nalazimo. Ako skočimo u funkciju, guramo njen unos na vrh steka. Kada se vratimo iz funkcije, izbacujemo najviši element iz steka i završavamo tamo odakle smo pozvali ovu funkciju. To je sve što stog može da uradi. A sada jedno veoma interesantno pitanje. Kako onda asinhroni funkcioniraju u JavasScript-u?

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

Zapravo, pored steka, pretraživači imaju poseban red za rad sa takozvanim WebAPI-jem. Funkcije iz ovog reda će se izvršavati po redoslijedu tek nakon što se stog potpuno očisti. Tek nakon toga se stavljaju iz reda u stog za izvršenje. Ako postoji barem jedan element na steku u ovom trenutku, onda oni ne mogu ući na stek. Upravo zbog toga, pozivanje funkcija po isteku je često netočno u vremenu, jer funkcija ne može doći iz reda u stog dok je puna.

Pogledajmo sljedeći primjer i idemo kroz njega korak po korak. Da vidimo i šta se dešava u sistemu.

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

1) Za sada se ništa ne dešava. Konzola pretraživača je čista, stek poziva je prazan.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

3) I ispunjeno je

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

6) Naredba setTimeout(function cb1() {… }) se izvršava. Pregledač kreira tajmer koji je dio Web API-ja. Izvršit će odbrojavanje.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

7) Komanda setTimeout(function cb1() {… }) je završila svoj rad i uklonjena je iz steka poziva.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

8) Naredba console.log('Bye') je dodana u stog poziva.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

9) Izvršava se naredba console.log('Bye').

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

11) Nakon što protekne najmanje 5000ms, tajmer se završava i stavlja cb1 povratni poziv u red za povratni poziv.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

12) Petlja događaja preuzima funkciju cb1 iz reda povratnog poziva i gura je u stek poziva.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

14) Izvršava se naredba console.log('cb1').

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

16) Funkcija cb1 je uklonjena iz steka poziva.

Pogledajmo primjer u dinamici:

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

Pa, pogledali smo kako je asinhronija implementirana u JavaScript-u. Hajdemo sada ukratko o evoluciji asinhronog koda.

Evolucija asinhronog 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 obaviti samo sa funkcijama. Mogu se proslijediti kao i svaka druga varijabla drugim funkcijama. Tako su rođeni povratni pozivi. I to je cool, zabavno i vatreno, sve dok se ne pretvori u tugu, melanholiju i tugu. Zašto? Da, jednostavno je:

  • Kako složenost koda raste, projekt se brzo pretvara u nejasne višestruko ugniježđene blokove - „pakao povratnog poziva“.
  • Rukovanje greškama se može lako previdjeti.
  • Ne možete vratiti izraze s return.

Dolaskom Promise-a 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, koji su poboljšali čitljivost koda
  • Postojala je posebna metoda presretanja grešaka
  • Paralelno izvršavanje sa dodanim Promise.all
  • Ugniježđenu asinhroniju možemo riješiti sa async/await

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

Pa, ovdje se glatko približavamo reaktivnom programiranju. Umoran? Pa, dobra stvar je što možete ići skuhati galebove, razmišljati i vratiti se da čitate više. I nastaviću.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

Reaktivno programiranje - paradigma programiranja fokusirana na tokove podataka i propagaciju promjena. Pogledajmo pobliže šta je tok podataka.

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

const eventsArray = [];

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

Zamislimo da imamo polje za unos. Kreiramo niz, a za svaki unos ulaznog događaja pohranit ćemo događaj u naš niz. Istovremeno, želio bih napomenuti da je naš niz sortiran po vremenu, tj. indeks kasnijih događaja je veći od indeksa ranijih. Takav niz je pojednostavljeni model toka podataka, ali još uvijek nije tok. Da bi se ovaj niz sigurno mogao nazvati streamom, mora 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 JavaScript-u (povratni poziv, obećanje, RxJs)

Protok je niz podataka sortiranih po vremenu koji može ukazivati ​​da su se podaci promijenili. Sada zamislite koliko je zgodno pisati kod u kojem trebate pokrenuti nekoliko događaja u različitim dijelovima koda za jednu akciju. Jednostavno se pretplatimo na stream i on će nam reći kada dođe do promjena. I RxJs biblioteka to može učiniti.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

RxJS je biblioteka za rad sa asinhronim programima i programima zasnovanim na događajima koji koriste vidljive sekvence. Biblioteka pruža glavni tip Posmatrano, nekoliko tipova pomoćnika (Posmatrači, Planeri, Subjekti) i operatori za rad sa događajima kao sa kolekcijama (mapa, filter, redukcija, svaki i slični iz JavaScript niza).

Hajde da razumemo osnovne koncepte ove biblioteke.

Opservable, Observer, Producer

Opservable je prvi osnovni tip koji ćemo pogledati. Ova klasa sadrži glavni dio implementacije RxJs. Pridružen je vidljivom toku, na koji se može pretplatiti korištenjem metode pretplate.

Observable implementira pomoćni mehanizam za kreiranje ažuriranja, tzv posmatrač. Poziva se izvor vrijednosti za promatrača producent. To može biti niz, iterator, web socket, neka vrsta događaja, itd. Dakle, možemo reći da je observable provodnik između Producenta i Observera.

Observable obrađuje tri vrste Observer događaja:

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

Pogledajmo demo:

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

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

Razmišljam naglas

A onda sam shvatio da je zanimljivije pričati nego pisati o tome. 😀

pretplata

Kada se pretplatimo na stream, kreiramo novu klasu pretplata, što nam daje mogućnost da otkažemo pretplatu na metodu odjavite se. Također možemo grupisati pretplate koristeći metodu dodati. Pa, logično je da možemo razgrupisati niti koristeći ukloniti. Metode dodavanja i uklanjanja prihvataju drugu pretplatu kao ulaz. Želio bih napomenuti da kada otkažemo pretplatu, poništavamo pretplatu na sve podređene pretplate kao da su oni također pozvali metodu odjave. Nastavi.

Vrste tokova

HOT
HLADNO

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

Podaci se prosljeđuju u vrijeme kreiranja promatranog
Podaci se dostavljaju u trenutku pretplate.

Treba više logike da odjavite pretplatu
Nit se sama završava

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

Sve pretplate imaju istu vrijednost
Pretplate su nezavisne

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

Da dam analogiju, zamislio bih vrući tok poput filma u bioskopu. U kom trenutku ste došli, od tog trenutka ste počeli da gledate. Uporedio bih hladan tok sa pozivom u njima. podrška. Svaki pozivalac sluša snimak telefonske sekretarice od početka do kraja, ali možete prekinuti vezu ako se odjavite.

Želio bih napomenuti da postoje i takozvani topli tokovi (svaku definiciju sam sreo izuzetno rijetko i samo u stranim zajednicama) - to je tok koji se iz hladnog pretvara u topli. Postavlja se pitanje - gdje koristiti)) Dat ću primjer iz prakse.

Radim sa Angularom. Aktivno koristi rxjs. Da bih dobio podatke na server, očekujem hladan tok i koristim ovaj stream u šablonu koristeći asyncPipe. Ako ovu cev koristim nekoliko puta, onda, vraćajući se na definiciju hladnog toka, svaka cev će tražiti podatke od servera, što je u najmanju ruku čudno. A ako pretvorim hladan tok u topli, tada će se zahtjev dogoditi jednom.

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

operatori

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 priliku da radimo sa streamovima. Oni pomažu u kontroli događaja koji teku u Observable-u. Razmotrit ćemo nekoliko najpopularnijih, a više informacija o operaterima možete pronaći na linkovima u korisnim informacijama.

Operateri-of

Počnimo s pomoćnim operatorom. Kreira Observable na osnovu jednostavne vrijednosti.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

Operatori-filter

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

Operator filtera, kao što ime govori, filtrira stream signal. Ako operator vrati true, onda preskače dalje.

Operateri - uzmi

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

take - Uzima vrijednost broja emisija, nakon čega se tok završava.

Operatori-debounceTime

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

debounceTime - odbacuje emitirane vrijednosti koje spadaju u određeni vremenski interval između izlaznih podataka - nakon što vremenski interval prođe, emituje 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 JavaScript-u (povratni poziv, obećanje, RxJs)

Operatori-takeWhile

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

Emituje vrijednosti sve dok takeWhile ne vrati false, a zatim otkaže pretplatu na nit.

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 JavaScript-u (povratni poziv, obećanje, RxJs)

Kombinacija operatoraNajnoviji

Kombinovani operator kombiLatest je donekle sličan obećanju.all. Kombinira više tokova u jedan. Nakon što je svaka nit napravila barem jedno emitovanje, dobijamo najnovije vrijednosti iz svake kao niz. Nadalje, nakon bilo kakvog emitiranja iz kombinovanih tokova, to će dati nove vrijednosti.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, 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 JavaScript-u (povratni poziv, obećanje, RxJs)

Operatori-zip

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, 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 JavaScript-u (povratni poziv, obećanje, RxJs)

Operateri - forkJoin

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

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, 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 JavaScript-u (povratni poziv, obećanje, RxJs)

Operateri-mapa

Operator transformacije mape transformira vrijednost emisije u novu.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, 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 JavaScript-u (povratni poziv, obećanje, RxJs)

Operateri - podijelite, dodirnite

Operator tap vam omogućava da izvršite nuspojave, odnosno sve radnje koje ne utiču na sekvencu.

Operater za dijeljenje može hladan tok pretvoriti u vrući.

Asinkrono programiranje u JavaScript-u (povratni poziv, obećanje, RxJs)

Operateri su gotovi. Pređimo na predmet.

Razmišljam naglas

A onda sam otišao da popijem čaj. Umorna sam od ovih primjera 😀

Predmetna porodica

Porodica predmeta je odličan primjer vrućih niti. Ove klase su neka vrsta hibrida koji istovremeno djeluju i kao promatrač i promatrač. Budući da je predmet vrući stream, morate ga otkazati. Ako govorimo o glavnim metodama, onda su to:

  • sljedeće - prosljeđivanje novih podataka u stream
  • greška - greška i prekid niti
  • kompletan - kraj niti
  • pretplatite se - pretplatite se na stream
  • odjaviti se - odjaviti se sa teme
  • asObservable - transformirajte se u posmatrača
  • toPromise - pretvara se u obećanje

Odredite 4 5 vrsta predmeta.

Razmišljam naglas

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

Simple Subject new Subject()- najjednostavnije vrste predmeta. Kreirano bez parametara. Prolazi vrijednosti koje su stigle tek nakon pretplate.

BehaviorSubject new BehaviorSubject( defaultData<T> ) - po mom mišljenju najčešći tip predmeta. Ulaz uzima zadanu vrijednost. Uvijek pohranjuje podatke posljednjeg izdanja, 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) - Opciono, može uzeti kao prvi argument veličinu bafera vrijednosti ​​koje će pohraniti u sebe, a drugi put tokom kojeg su nam potrebne promjene.

asyncsubject new AsyncSubject() - ništa se ne događa kada se pretplatite, a vrijednost će biti vraćena tek kada se završi. Biće vraćena samo posljednja vrijednost toka.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - Dokumentacija o tome šuti i ja to prvi put vidim. Ko zna čime se bavi, napišite, dodaćemo.

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

korisne informacije

izvor: www.habr.com

Dodajte komentar