Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Hola a tots. En contacte Omelnitsky Sergey. No fa gaire, vaig organitzar un flux sobre programació reactiva, on vaig parlar de l'asincronia en JavaScript. Avui m'agradaria resumir aquest material.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Però abans de començar el material principal, hem de fer una introducció. Per tant, comencem amb les definicions: què són la pila i la cua?

Pila és una col·lecció els elements de la qual es recuperen segons LIFO "últim en entrar, primer en sortir".

Cua és una col·lecció els elements de la qual s'obtenen segons el principi FIFO ("primer en entrar, primer en sortir".

D'acord, continuem.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

JavaScript és un llenguatge de programació d'un sol fil. Això vol dir que només té un fil d'execució i una pila on les funcions es posen a la cua per a l'execució. Per tant, JavaScript només pot realitzar una operació alhora, mentre que altres operacions esperaran el seu torn a la pila fins que se'ls crida.

Pila de trucades és una estructura de dades que, en termes senzills, registra informació sobre el lloc del programa on ens trobem. Si saltem a una funció, empenyem la seva entrada a la part superior de la pila. Quan tornem d'una funció, aixequem l'element superior de la pila i acabem des d'on hem cridat aquesta funció. Això és tot el que pot fer la pila. I ara una pregunta molt interessant. Llavors, com funciona la asincronia a JavasScript?

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

De fet, a més de la pila, els navegadors tenen una cua especial per treballar amb l'anomenada WebAPI. Les funcions d'aquesta cua s'executaran en ordre només després d'esborrar completament la pila. Només després d'això es col·loquen de la cua a la pila per a l'execució. Si hi ha almenys un element a la pila en aquest moment, no poden entrar a la pila. Només per això, trucar a funcions per temps d'espera és sovint inexacte en el temps, ja que la funció no pot passar de la cua a la pila mentre està plena.

Fem un cop d'ull a l'exemple següent i ho repassem pas a pas. Vegem també què passa al sistema.

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

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

1) Fins ara no ha passat res. La consola del navegador està neta, la pila de trucades està buida.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

2) A continuació, s'afegeix l'ordre console.log('Hola') a la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

3) I es compleix

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

4) A continuació, console.log('Hola') s'elimina de la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

5) Ara passem a l'ordre setTimeout(function cb1() {… }). S'afegeix a la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

6) S'executa l'ordre setTimeout(function cb1() {… }). El navegador crea un temporitzador que forma part de l'API web. Es farà un compte enrere.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

7) L'ordre setTimeout(function cb1() {… }) ha completat el seu treball i s'ha eliminat de la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

8) L'ordre console.log('Bye') s'afegeix a la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

9) S'executa l'ordre console.log('Adéu').

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

10) L'ordre console.log('Bye') s'elimina de la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

11) Després d'haver transcorregut almenys 5000 ms, el temporitzador finalitza i posa la devolució de trucada cb1 a la cua de devolució de trucada.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

12) El bucle d'esdeveniments agafa la funció cb1 de la cua de devolució de trucada i l'empeny a la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

13) La funció cb1 s'executa i afegeix console.log('cb1') a la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

14) S'executa l'ordre console.log('cb1').

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

15) L'ordre console.log('cb1') s'elimina de la pila de trucades.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

16) La funció cb1 s'elimina de la pila de trucades.

Vegem un exemple en dinàmica:

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Bé, vam mirar com s'implementa l'asincronia a JavaScript. Ara parlem breument de l'evolució del codi asíncron.

L'evolució del codi asíncron.

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

La programació asíncrona tal com la coneixem a JavaScript només es pot fer amb funcions. Es poden passar com qualsevol altra variable a altres funcions. Així és com van néixer les devolucions de trucada. I és fresc, divertit i fervorós, fins que es converteix en tristesa, malenconia i tristesa. Per què? Sí, és senzill:

  • A mesura que la complexitat del codi creix, el projecte es converteix ràpidament en múltiples blocs imbricats obscurs: "callback hell".
  • La gestió d'errors es pot passar per alt fàcilment.
  • No podeu tornar expressions amb return.

Amb l'arribada de la Promesa, la situació ha millorat una mica.

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

  • Van aparèixer les cadenes de promeses, que van millorar la llegibilitat del codi
  • Hi havia un mètode separat d'intercepció d'errors
  • Execució paral·lela amb Promise.all afegit
  • Podem resoldre l'asincronia imbricada amb async/wait

Però la promesa té les seves limitacions. Per exemple, una promesa, sense ballar amb un tamborí, no es pot cancel·lar i, el més important, funciona amb un valor.

Bé, aquí ens apropem sense problemes a la programació reactiva. Cansat? Bé, el bo és que podeu anar a fer unes gavines, fer una pluja d'idees i tornar a llegir-ne més. I continuaré.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Programació reactiva - un paradigma de programació centrat en els fluxos de dades i la propagació de canvis. Fem una ullada més de prop a què és un flux de dades.

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

const eventsArray = [];

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

Imaginem que tenim un camp d'entrada. Creem una matriu i per a cada teclat de l'esdeveniment d'entrada, emmagatzemarem l'esdeveniment a la nostra matriu. Al mateix temps, m'agradaria assenyalar que la nostra matriu està ordenada per temps, és a dir. l'índex d'esdeveniments posteriors és més gran que l'índex dels anteriors. Aquesta matriu és un model de flux de dades simplificat, però encara no és un flux. Perquè aquesta matriu s'anomena de manera segura un flux, ha de ser capaç d'informar d'alguna manera als subscriptors que hi han arribat dades noves. Així arribem a la definició de flux.

Flux de dades

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

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

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Flux és una matriu de dades ordenades per temps que pot indicar que les dades han canviat. Ara imagineu-vos com de convenient esdevé escriure codi en el qual necessiteu activar diversos esdeveniments en diferents parts del codi per a una acció. Simplement ens subscrivim al flux i ens indicarà quan hi hagi canvis. I la biblioteca RxJs pot fer-ho.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

RxJS és una biblioteca per treballar amb programes asíncrons i basats en esdeveniments mitjançant seqüències observables. La biblioteca proporciona el tipus principal observable, diversos tipus d'ajuda (Observadors, programadors, subjectes) i operadors per treballar amb esdeveniments com amb col·leccions (mapa, filtra, redueix, cada i altres similars de JavaScript Array).

Entendrem els conceptes bàsics d'aquesta biblioteca.

Observable, observador, productor

L'observable és el primer tipus de base que veurem. Aquesta classe conté la part principal de la implementació RxJs. S'associa a un flux observable, al qual es pot subscriure mitjançant el mètode de subscripció.

Observable implementa un mecanisme auxiliar per a la creació d'actualitzacions, l'anomenat Observador. La font de valors per a un observador s'anomena Productor. Pot ser una matriu, un iterador, un sòcol web, algun tipus d'esdeveniment, etc. Així que podem dir que observable és un conductor entre Productor i Observador.

Observable gestiona tres tipus d'esdeveniments d'Observer:

  • següent: dades noves
  • error: un error si la seqüència ha finalitzat a causa d'una excepció. aquest esdeveniment també implica el final de la seqüència.
  • complete: un senyal sobre el final de la seqüència. Això vol dir que no hi haurà més dades noves

Vegem una demostració:

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Al principi processarem els valors 1, 2, 3, i després d'1 seg. obtenim 4 i acabem el nostre fil.

Pensant en veu alta

I llavors em vaig adonar que era més interessant explicar-ho que escriure sobre això. 😀

subscripció

Quan ens subscrivim a un flux, creem una nova classe subscripció, que ens dóna l'opció de donar-nos de baixa amb el mètode donar-se de baixa. També podem agrupar les subscripcions mitjançant el mètode afegir. Bé, és lògic que puguem desagrupar fils utilitzant treure. Els mètodes d'afegir i eliminar accepten una subscripció diferent com a entrada. M'agradaria tenir en compte que quan ens donem de baixa, ens donem de baixa de totes les subscripcions infantils com si també anomenessin el mètode de cancel·lació. Endavant.

Tipus de corrents

CALENT
REFREDAT

El productor es crea fora de l'observable
El productor es crea dins de l'observable

Les dades es passen en el moment en què es crea l'observable
Les dades es proporcionen en el moment de la subscripció.

Necessites més lògica per donar-te de baixa
El fil acaba per si mateix

Utilitza una relació d'un a molts
Utilitza una relació un a un

Totes les subscripcions tenen el mateix valor
Les subscripcions són independents

Les dades es poden perdre si no hi ha subscripció
Torna a emetre tots els valors del flux per a una subscripció nova

Per fer una analogia, m'imaginaria un flux calent com una pel·lícula al cinema. En quin moment vas venir, a partir d'aquell moment vas començar a mirar. Compararia un corrent fred amb una trucada en aquests. suport. Qualsevol persona que truca escolta la gravació del contestador automàtic de principi a fi, però podeu penjar amb la cancel·lació de la subscripció.

M'agradaria assenyalar que també hi ha els anomenats rierols càlids (he trobat aquesta definició molt poques vegades i només en comunitats estrangeres): aquest és un rierol que es transforma d'un corrent fred a un de calent. Sorgeix la pregunta: on utilitzar-lo)) Posaré un exemple de la pràctica.

Estic treballant amb Angular. Utilitza activament rxjs. Per obtenir dades al servidor, espero un flux fred i utilitzo aquest flux a la plantilla mitjançant asyncPipe. Si faig servir aquesta canonada diverses vegades, aleshores, tornant a la definició d'un flux fred, cada canonada demanarà dades al servidor, la qual cosa és estrany com a mínim. I si converto un corrent fred en un de càlid, la sol·licitud es produirà una vegada.

En general, entendre el tipus de fluxos és bastant difícil per als principiants, però important.

Operadors

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

Els operadors ens ofereixen l'oportunitat de treballar amb fluxos. Ajuden a controlar els esdeveniments que flueixen a l'Observable. Considerarem un parell de les més populars, i es pot trobar més informació sobre els operadors als enllaços d'informació útil.

Operadors-de

Comencem amb l'operador auxiliar de. Crea un Observable basat en un valor simple.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Operadors-filtre

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

L'operador de filtre, com el seu nom indica, filtra el senyal de flux. Si l'operador retorna true, aleshores salta més.

Operadors - prendre

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

take - Pren el valor del nombre d'emissions, després del qual s'acaba el flux.

Operadors-debonceTime

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

debounceTime: descarta els valors emesos que es troben dins de l'interval de temps especificat entre les dades de sortida; un cop transcorregut l'interval de temps, emet l'últim valor.

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

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Operadors-takeWhile

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Emet valors fins que takeWhile retorna false i, a continuació, cancel·la la subscripció al flux.

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

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Operadors-combineLatest

L'operador combinat combineLatest és una mica semblant a promise.all. Combina diversos fluxos en un sol. Després que cada fil hagi fet almenys una emissió, obtenim els valors més recents de cadascun com a matriu. A més, després de qualsevol emissió dels fluxos combinats, donarà nous valors.

Programació asíncrona en 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));

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Operadors-zip

Zip: espera un valor de cada flux i forma una matriu basada en aquests valors. Si el valor no prové de cap fil, el grup no es formarà.

Programació asíncrona en 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));

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Operadors - forkJoin

forkJoin també uneix fils, però només emet un valor quan tots els fils estan complets.

Programació asíncrona en 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);

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Operadors-mapa

L'operador de transformació del mapa transforma el valor d'emissió en un de nou.

Programació asíncrona en 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)
);

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Operadors: comparteix, toca

L'operador de toc permet fer efectes secundaris, és a dir, qualsevol acció que no afecti la seqüència.

L'operador d'utilitat compartida pot convertir un corrent fred en un corrent calent.

Programació asíncrona en JavaScript (Callback, Promise, RxJs)

Els operadors estan acabats. Passem al tema.

Pensant en veu alta

I després vaig anar a prendre un te. Estic cansat d'aquests exemples 😀

Família subjecta

La família de temes és un bon exemple de fils calents. Aquestes classes són una mena d'híbrids que actuen com a observable i observador alhora. Com que el tema és un flux calent, s'ha de cancel·lar la subscripció. Si parlem dels principals mètodes, aquests són:

  • següent: passar dades noves al flux
  • error - error i finalització del fil
  • complet - final del fil
  • subscriure - subscriure's a un flux
  • unsubscribe: cancel·la la subscripció al flux
  • asObservable: transformar-se en observador
  • toPromise - es transforma en una promesa

Assignar 4 5 tipus de matèries.

Pensant en veu alta

Vaig dir 4 al corrent, però va resultar que n'hi van afegir un més. Com diu la dita, viu i aprèn.

Tema simple new Subject()- les assignatures més senzilles. Creat sense paràmetres. Passa els valors que van arribar només després de la subscripció.

ComportamentSubjecte new BehaviorSubject( defaultData<T> ) - al meu entendre el tipus més comú de subjectes-s. L'entrada pren el valor per defecte. Desa sempre les dades de l'últim número, que es transmeten en subscriure's. Aquesta classe també té un mètode de valor útil que retorna el valor actual del flux.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Opcionalment, pot prendre com a primer argument la mida del buffer de valors que emmagatzemarà en si mateix, i el segon temps durant el qual necessitem canvis.

subjecte asíncron new AsyncSubject() - no passa res en subscriure's i el valor només es retornarà quan estigui complet. Només es retornarà l'últim valor del flux.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - La documentació en calla i jo mateix la veig per primera vegada. Qui sap què fa, escriu, afegirem.

Uf. Bé, hem considerat tot el que volia explicar avui. Espero que aquesta informació hagi estat útil. Podeu llegir la llista de literatura pel vostre compte a la pestanya Informació útil.

Informació útil

Font: www.habr.com

Afegeix comentari