Programare asincronă în JavaScript (Callback, Promise, RxJs)

Salutare tuturor. Serghei Omelnitsky este în legătură. Nu cu mult timp în urmă am găzduit un flux despre programarea reactivă, unde am vorbit despre asincronie în JavaScript. Astăzi aș dori să iau notițe despre acest material.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Dar înainte de a începe materialul principal, trebuie să facem o notă introductivă. Deci, să începem cu definiții: ce este o stivă și o coadă?

Grămadă este o colecție ale cărei elemente sunt obținute pe baza LIFO ultimul intrat, primul ieșit

Întoarce-te este o colecție ale cărei elemente sunt obținute pe baza FIFO primul intrat, primul ieșit

Bine, hai să continuăm.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

JavaScript este un limbaj de programare cu un singur thread. Aceasta înseamnă că există un singur fir de execuție și o stivă pe care funcțiile sunt puse în coadă pentru execuție. Prin urmare, JavaScript poate efectua o singură operație la un moment dat, în timp ce alte operațiuni își vor aștepta rândul pe stivă până când sunt apelate.

Stack de apeluri este o structură de date care, simplu spus, înregistrează informații despre locul din programul în care ne aflăm. Dacă trecem într-o funcție, împingem intrarea acesteia în partea de sus a stivei. Când ne întoarcem de la o funcție, scoatem elementul cel mai de sus din stivă și ajungem înapoi acolo unde am numit funcția. Acesta este tot ceea ce poate face stiva. Și acum o întrebare extrem de interesantă. Cum funcționează atunci asincronia în JavasScript?

Programare asincronă în JavaScript (Callback, Promise, RxJs)

De fapt, pe lângă stivă, browserele au o coadă specială pentru lucrul cu așa-numitul WebAPI. Funcțiile din această coadă vor fi executate în ordine numai după ce stiva a fost complet golită. Abia după aceasta sunt împinși din coadă în stivă pentru execuție. Dacă există cel puțin un element pe stivă în acest moment, atunci nu pot fi adăugate la stivă. Tocmai din acest motiv, apelarea funcțiilor prin timeout nu este adesea precisă în timp, deoarece funcția nu poate ajunge din coadă în stivă cât timp este plină.

Рассмотрим следующий пример и займёмся его пошаговым «выполнением». Также посмотрим, что при этом происходит в системе.

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

Programare asincronă în JavaScript (Callback, Promise, RxJs)

1) Încă nu se întâmplă nimic. Consola browserului este clară, stiva de apeluri este goală.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

2) Apoi comanda console.log('Hi') este adăugată la stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

3) Și se împlinește

Programare asincronă în JavaScript (Callback, Promise, RxJs)

4) Apoi console.log('Hi') este eliminat din stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

5) Acum treceți la comanda setTimeout(funcția cb1() {… }). Este adăugat la stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

6) Comanda setTimeout(function cb1() {… }) este executată. Browserul creează un temporizator care face parte din API-ul web. Va efectua o numărătoare inversă.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

7) Comanda setTimeout(function cb1() {... }) și-a încheiat activitatea și este eliminată din stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

8) Comanda console.log('Bye') este adăugată la stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

9) Comanda console.log('Bye') este executată.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

10) Comanda console.log('Bye') este eliminată din stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

11) După ce au trecut cel puțin 5000 ms, temporizatorul se termină și plasează callback cb1 în coada de callback.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

12) Bucla de evenimente preia funcția cb1 din coada de apel invers și o plasează în stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

13) Funcția cb1 este executată și adaugă console.log('cb1') la stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

14) Comanda console.log('cb1') este executată.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

15) Comanda console.log('cb1') este eliminată din stiva de apeluri.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

16) Funcția cb1 este eliminată din stiva de apeluri.

Să ne uităm la un exemplu în dinamică:

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Ei bine, ne-am uitat la modul în care este implementată asincronia în JavaScript. Acum să vorbim pe scurt despre evoluția codului asincron.

Evoluția codului asincron.

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

Programarea asincronă așa cum o cunoaștem în JavaScript poate fi implementată numai prin funcții. Ele pot fi transmise ca orice altă variabilă altor funcții. Așa s-au născut apelurile inverse. Și este cool, distractiv și jucăuș, până când se transformă în tristețe, melancolie și tristețe. De ce? E simplu:

  • Pe măsură ce complexitatea codului crește, proiectul se transformă rapid în blocuri obscure, imbricate în mod repetat - „callback hell”.
  • Gestionarea erorilor poate fi ușor de ratat.
  • Nu puteți returna expresii cu return.

Odată cu apariția Promisei, situația a devenit puțin mai bună.

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

  • Au apărut lanțuri de promisiuni, care au îmbunătățit lizibilitatea codului
  • A apărut o metodă separată de captare a erorilor
  • S-a adăugat posibilitatea de execuție paralelă folosind Promise.all
  • Putem rezolva asincronia imbricată folosind async/wait

Dar promisiunile au limitele lor. De exemplu, o promisiune nu poate fi anulată fără a dansa cu o tamburină și cel mai important este că funcționează cu o singură valoare.

Ei bine, ne-am abordat fără probleme programarea reactivă. Obosit? Ei bine, din fericire, poți să faci niște ceai, să te gândești și să te întorci să citești mai multe. Și voi continua.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Programare reactiva este o paradigmă de programare axată pe fluxurile de date și propagarea schimbărilor. Să aruncăm o privire mai atentă la ce este un flux de date.

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

const eventsArray = [];

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

Să ne imaginăm că avem un câmp de intrare. Creăm o matrice și pentru fiecare tastare a evenimentului de intrare vom stoca evenimentul în matricea noastră. În același timp, aș dori să remarc că matricea noastră este sortată în funcție de timp, de exemplu. indicele evenimentelor ulterioare este mai mare decât indicele evenimentelor anterioare. O astfel de matrice este un model simplificat al unui flux de date, dar nu este încă un flux. Pentru ca această matrice să fie numită în siguranță flux, trebuie să poată informa cumva abonații că au sosit date noi în ea. Ajungem astfel la definiția fluxului.

Flux de date

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

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

Programare asincronă în JavaScript (Callback, Promise, RxJs)

curent este o serie de date sortate după timp, care poate indica faptul că datele s-au schimbat. Acum imaginați-vă cât de convenabil devine să scrieți cod în care o acțiune necesită apelarea mai multor evenimente în diferite secțiuni ale codului. Pur și simplu ne abonam la flux și ne va anunța când apar modificări. Și biblioteca RxJs poate face acest lucru.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

RxJS este o bibliotecă pentru lucrul cu programe asincrone și bazate pe evenimente folosind secvențe observabile. Biblioteca oferă un tip de bază Observabil, mai multe tipuri auxiliare (Observator, programatori, subiecte) și operatori pentru lucrul cu evenimente ca și cu colecții (hartă, filtrează, reduce, fiecare și altele similare din JavaScript Array).

Să înțelegem conceptele de bază ale acestei biblioteci.

Observabil, Observator, Producător

Observabil este primul tip de bază pe care îl vom analiza. Această clasă conține partea principală a implementării RxJs. Este asociat cu un flux observabil, la care se poate abona folosind metoda abonamentului.

Observable implementează un mecanism de ajutor pentru crearea actualizărilor, așa-numitul Observator. Se numește sursa de valori pentru Observator Producător. Acesta ar putea fi o matrice, iterator, socket web, un fel de eveniment etc. Deci putem spune că observabil este un dirijor între Producer și Observer.

Observable gestionează trei tipuri de evenimente Observer:

  • următorul – date noi
  • eroare – o eroare dacă secvența sa încheiat din cauza unei excepții. acest eveniment implică și finalizarea secvenței.
  • complete — semnal despre finalizarea secvenței. Aceasta înseamnă că nu vor mai exista date noi.

Să vedem demonstrația:

Programare asincronă în JavaScript (Callback, Promise, RxJs)

La început vom procesa valorile 1, 2, 3 și după 1 secundă. vom obține 4 și vom termina fluxul nostru.

Gândind cu voce tare

Și apoi mi-am dat seama că a spune asta era mai interesant decât a scrie despre asta. 😀

Abonament

Când ne abonam la un flux, creăm o nouă clasă abonamentceea ce ne oferă posibilitatea de a ne dezabona folosind metoda dezabonare. De asemenea, putem grupa abonamente folosind metoda adăuga. Ei bine, este logic să putem degrupa firele folosind scoate. Metodele de adăugare și eliminare acceptă un alt abonament ca intrare. Aș dori să notez că atunci când ne dezabonăm, ne dezabonăm de la toate abonamentele pentru copii ca și cum ar fi apelat la metoda de dezabonare. Daţi-i drumul.

Tipuri de fluxuri

FIERBINTE
RECE

Producătorul este creat în afara observabilului
Producătorul este creat în interiorul observabil

Datele sunt transferate în momentul în care observabilul este creat
Datele sunt furnizate în momentul abonării

Aveți nevoie de o logică suplimentară pentru dezabonare
Firul se termină de la sine

Folosește o relație unu-la-mai mulți
Folosește o relație unu-la-unu

Toate abonamentele au aceeași semnificație
Abonamentele sunt independente

Datele se pot pierde dacă nu aveți un abonament
Reemite toate valorile fluxului pentru un nou abonament

Pentru a da o analogie, m-aș gândi la un flux fierbinte ca la un film într-un teatru. În ce moment ai ajuns, din acel moment ai început să te uiți. Aș compara un flux rece cu un apel în tehnologie. a sustine. Orice apelant ascultă înregistrarea mesageriei vocale de la început până la sfârșit, dar puteți închide folosind dezabonare.

Aș dori să remarc că există și așa-numitele fluxuri calde (am întâlnit această definiție extrem de rar și doar în comunitățile străine) - acesta este un flux care se transformă dintr-un flux rece în unul cald. Apare întrebarea - unde se folosește)) Voi da un exemplu din practică.

Lucrez cu Angular. El folosește activ rxjs. Pentru a primi date pe server, mă aștept la un fir rece și folosesc acest fir în șablon folosind asyncPipe. Dacă folosesc această conductă de mai multe ori, atunci, revenind la definiția unui flux rece, fiecare conductă va solicita date de la server, ceea ce este cel puțin ciudat. Și dacă transform un flux rece într-unul cald, atunci cererea se va întâmpla o dată.

În general, înțelegerea tipului de fluxuri este destul de dificilă pentru începători, dar importantă.

Operatorii

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

Operatorii ne oferă capacitatea de a ne extinde capacitatea de a lucra cu fluxuri. Ele ajută la controlul evenimentelor care au loc în Observable. Ne vom uita la câteva dintre cele mai populare, iar mai multe detalii despre operatori pot fi găsite folosind linkurile din informațiile utile.

Operatori - de

Să începem cu operatorul auxiliar al. Acesta creează un Observabil bazat pe o valoare simplă.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Operatori - filtru

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Operatorul de filtrare, după cum sugerează și numele, filtrează semnalul fluxului. Dacă operatorul returnează true, acesta sare mai departe.

Operatori - luați

Programare asincronă în JavaScript (Callback, Promise, RxJs)

take — Preia valoarea numărului de emițători, după care firul se termină.

Operatori - debounceTime

Programare asincronă în JavaScript (Callback, Promise, RxJs)

debounceTime - elimină valorile emise care se încadrează în intervalul de timp specificat între ieșiri - după ce intervalul de timp a trecut, emite ultima valoare.

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

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Operatori - takeWhile

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Emite valori până când takeWhile returnează false, după care se dezabonează de la 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 )
);  

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Operatori - combineLatest

Комбинированный оператор combineLatest чем-то похож на promise.all. Он объединяет несколько потоков в один. После того как каждый поток сделает хотя бы один эмит, мы получаем последние значения от каждого в виде массива. Далее, после любого эмита из объединённых потоков он будет отдавать новые значения.

Programare asincronă în 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));

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Operatori - zip

Zip - Așteaptă o valoare din fiecare fir și formează o matrice pe baza acestor valori. Dacă valoarea nu provine din niciun fir, atunci grupul nu va fi format.

Programare asincronă în 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));

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Operatori - forkJoin

forkJoin unește și firele de execuție, dar emite o valoare numai atunci când toate firele de execuție sunt complete.

Programare asincronă în 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);

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Operatori – hartă

Operatorul de transformare a hărții transformă valoarea emițătorului într-una nouă.

Programare asincronă în 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)
);

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Operatori – partajați, atingeți

Operatorul de atingere vă permite să faceți efecte secundare, adică orice acțiuni care nu afectează secvența.

Operatorul de utilități partajate poate transforma un flux rece în unul cald.

Programare asincronă în JavaScript (Callback, Promise, RxJs)

Am terminat cu operatorii. Să trecem la Subiect.

Gândind cu voce tare

Și apoi m-am dus să beau un ceai. M-am săturat de aceste exemple 😀

Familia subiectului

Familia subiectului este un prim exemplu de fluxuri fierbinți. Aceste clase sunt un fel de hibrid care acționează simultan ca observabil și observator. Deoarece subiectul este un fir fierbinte, este necesar să vă dezabonați de la acesta. Dacă vorbim despre principalele metode, atunci acestea sunt:

  • următorul – transferul de date noi în flux
  • eroare – eroare și terminarea firului
  • complet – finalizarea firului
  • abonați-vă – abonați-vă la un flux
  • dezabonare – dezabonare de la flux
  • asObservable – se transformă într-un observator
  • toPromise – se transformă într-o promisiune

Există 4 5 tipuri de subiecte.

Gândind cu voce tare

Erau 4 persoane care vorbeau pe flux, dar s-a dovedit că au adăugat încă una. După cum se spune, trăiește și învață.

Subiect simplu new Subject()– cel mai simplu tip de subiecte. Creat fără parametri. Transmite valorile primite numai după abonament.

Comportament Subiect new BehaviorSubject( defaultData<T> ) – după părerea mea, cel mai frecvent tip de subiect. Intrarea ia valoarea implicită. Salvează întotdeauna datele ultimului număr, care sunt transmise la abonare. Această clasă are și o metodă de valoare utilă, care returnează valoarea curentă a fluxului.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) — Intrarea poate lua opțional ca prim argument dimensiunea bufferului de valori pe care o va stoca în sine, iar al doilea timpul în care avem nevoie de modificări.

AsyncSubject new AsyncSubject() — nu se întâmplă nimic la abonare, iar valoarea va fi returnată doar când este completă. Doar ultima valoare a fluxului va fi returnată.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) — Documentația tace despre el și îl văd pentru prima dată. Dacă știe cineva ce face, vă rugăm să scrieți și îl vom adăuga.

Uf. Ei bine, am acoperit tot ce am vrut să vă spun astăzi. Sper că această informație a fost de folos. Puteți citi singur lista de referințe în fila de informații utile.

informaţii utile

Sursa: www.habr.com

Adauga un comentariu