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

Ola a todos. En contacto Omelnitsky Sergey. Non hai moito tempo, aloxei un fluxo sobre programación reactiva, onde falei sobre a asincronía en JavaScript. Hoxe gustaríame resumir este material.

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

Pero antes de comezar o material principal, necesitamos facer unha introdución. Entón, imos comezar coas definicións: que son a pila e a cola?

Pila é unha colección cuxos elementos son recuperados en base a LIFO "último en entrar, primeiro en saír".

Quenda é unha colección cuxos elementos se obteñen segundo o principio FIFO ("primeiro en entrar, primeiro en saír".

Vale, seguimos.

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

JavaScript é unha linguaxe de programación dun só fío. Isto significa que só ten un fío de execución e unha pila onde as funcións están en cola para a súa execución. Polo tanto, JavaScript só pode realizar unha operación á vez, mentres que outras operacións agardarán a súa quenda na pila ata que sexan chamadas.

Pila de chamadas é unha estrutura de datos que, en termos sinxelos, rexistra información sobre o lugar do programa onde estamos. Se saltamos a unha función, empurramos a súa entrada ata o principio da pila. Cando volvemos dunha función, sacamos o elemento superior da pila e rematamos onde chamamos a esta función. Iso é todo o que pode facer a pila. E agora unha pregunta moi interesante. Como funciona entón a asincronía en JavasScript?

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

De feito, ademais da pila, os navegadores teñen unha cola especial para traballar coa chamada WebAPI. As funcións desta cola executaranse en orde só despois de que a pila estea completamente limpa. Só despois diso colócanse desde a cola na pila para a súa execución. Se hai polo menos un elemento na pila neste momento, entón non poden entrar na pila. Só por iso, a chamada de funcións por tempo de espera adoita ser inexacta no tempo, xa que a función non pode pasar da cola á pila mentres está chea.

Vexamos o seguinte exemplo e repasámolo paso a paso. Vexamos tamén que pasa no sistema.

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

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

1) Polo de agora non está a pasar nada. A consola do navegador está limpa, a pila de chamadas está baleira.

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

2) Despois engádese o comando console.log('Ola') á pila de chamadas.

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

3) E cúmprese

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

4) A continuación, elimínase console.log('Ola') da pila de chamadas.

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

5) Agora imos pasar ao comando setTimeout(function cb1() {… }). Engádese á pila de chamadas.

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

6) Exécutase o comando setTimeout(function cb1() {… }). O navegador crea un temporizador que forma parte da API web. Realizará unha conta atrás.

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

7) O comando setTimeout(function cb1() {… }) completou o seu traballo e eliminouse da pila de chamadas.

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

8) O comando console.log('Bye') engádese á pila de chamadas.

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

9) Exécutase o comando console.log('Bye').

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

10) Elimínase o comando console.log('Bye') da pila de chamadas.

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

11) Despois de que transcorran polo menos 5000 ms, o temporizador finaliza e pon a devolución de chamada cb1 na cola de devolución de chamada.

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

12) O bucle de eventos toma a función cb1 da cola de devolución de chamadas e empúxaa á pila de chamadas.

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

13) Exécutase a función cb1 e engade console.log('cb1') á pila de chamadas.

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

14) Exécutase o comando console.log('cb1').

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

15) Elimínase o comando console.log('cb1') da pila de chamadas.

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

16) Elimínase a función cb1 da pila de chamadas.

Vexamos un exemplo en dinámica:

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

Ben, miramos como se implementa a asincronía en JavaScript. Agora imos falar brevemente da evolución do código asíncrono.

A evolución do código asíncrono.

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

A programación asíncrona tal e como a coñecemos en JavaScript só se pode facer con funcións. Pódense pasar como calquera outra variable a outras funcións. Así naceron as devolucións de chamada. E mola, divertido e fervoroso, ata converterse en tristeza, melancolía e tristeza. Por que? Si, é sinxelo:

  • A medida que crece a complexidade do código, o proxecto convértese rapidamente en varios bloques aniñados escuros: "callback hell".
  • O tratamento de erros pódese pasar por alto facilmente.
  • Non podes devolver expresións con return.

Coa chegada da Promesa, a situación mellorou un pouco.

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

  • Apareceron cadeas de promesas, que melloraron a lexibilidade do código
  • Había un método separado de interceptación de erros
  • Execución paralela con Promise.all engadido
  • Podemos resolver a asincronía anidada con async/wait

Pero a promesa ten as súas limitacións. Por exemplo, unha promesa, sen bailar cunha pandeireta, non se pode cancelar e, o máis importante, funciona cun valor.

Ben, aquí estamos achegándonos sen problemas á programación reactiva. Canso? Pois o bo é que podes ir facer unhas gaivotas, facer unha chuvia de ideas e volver a ler máis. E seguirei.

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

Programación reactiva - un paradigma de programación centrado nos fluxos de datos e na propagación de cambios. Vexamos máis de cerca o que é un fluxo de datos.

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

const eventsArray = [];

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

Imaxinemos que temos un campo de entrada. Creamos unha matriz e para cada teclado do evento de entrada, almacenaremos o evento na nosa matriz. Ao mesmo tempo, gustaríame sinalar que a nosa matriz está ordenada por tempo, é dicir. o índice de sucesos posteriores é maior que o índice de sucesos anteriores. Tal matriz é un modelo de fluxo de datos simplificado, pero aínda non é un fluxo. Para que esta matriz se chame con seguridade un fluxo, debe poder informar dalgunha maneira aos subscritores de que chegaron novos datos nela. Así chegamos á definición de fluxo.

Fluxo de datos

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

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

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

Fluxo é unha matriz de datos ordenados por tempo que pode indicar que os datos cambiaron. Agora imaxina o conveniente que se fai escribir código no que hai que activar varios eventos en diferentes partes do código para unha acción. Simplemente subscribimos ao fluxo e indicaranos cando se produzan cambios. E a biblioteca RxJs pode facelo.

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

RxJS é unha biblioteca para traballar con programas asíncronos e baseados en eventos utilizando secuencias observables. A biblioteca ofrece o tipo principal observable, varios tipos de axudantes (Observadores, Programadores, Suxeitos) e operadores para traballar con eventos como con coleccións (mapear, filtrar, reducir, cada e outros similares de JavaScript Array).

Imos entender os conceptos básicos desta biblioteca.

Observable, Observador, Produtor

O observable é o primeiro tipo de base que veremos. Esta clase contén a parte principal da implementación de RxJs. Está asociado a un fluxo observable, ao que se pode subscribir mediante o método de subscrición.

Observable implementa un mecanismo auxiliar para a creación de actualizacións, o chamado Observador. A fonte de valores para un observador chámase Productor. Pode ser unha matriz, un iterador, un socket web, algún tipo de evento, etc. Así que podemos dicir que observable é un condutor entre Producer e Observer.

Observable manexa tres tipos de eventos Observer:

  • seguinte - novos datos
  • erro: un erro se a secuencia rematou debido a unha excepción. este acontecemento tamén implica o final da secuencia.
  • complete - un sinal sobre o final da secuencia. Isto significa que non haberá máis datos novos

Vexamos unha demo:

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

Ao principio procesaremos os valores 1, 2, 3 e despois de 1 segundo. sacamos 4 e rematamos o noso fío.

Pensando en voz alta

E entón decateime de que era máis interesante contar que escribir sobre iso. 😀

Sinatura

Cando nos subscribimos a un fluxo, creamos unha nova clase sinatura, que nos dá a opción de darnos de baixa co método dar de baixa. Tamén podemos agrupar subscricións mediante o método engadir. Ben, é lóxico que poidamos desagrupar fíos usando eliminar. Os métodos de engadir e eliminar aceptan unha subscrición diferente como entrada. Gustaríame sinalar que cando cancelamos a subscrición, cancelamos a subscrición de todas as subscricións infantís coma se tamén chamasen ao método de cancelación da subscrición. Adiante.

Tipos de correntes

HOT
FRÍA

O produtor créase fóra do observable
O produtor é creado dentro do observable

Os datos pásanse no momento en que se crea o observable
Os datos son proporcionados no momento da subscrición.

Necesita máis lóxica para cancelar a subscrición
O fío remata por si só

Usa unha relación de un a moitos
Usa unha relación un a un

Todas as subscricións teñen o mesmo valor
As subscricións son independentes

Os datos pódense perder se non hai subscrición
Reemite todos os valores de fluxo para unha nova subscrición

Para facer unha analoxía, imaxinaría un fluxo quente como unha película nun cine. En que momento chegaches, a partir dese momento empezaches a mirar. Eu compararía unha corrente fría cunha chamada nesas. apoiar. Calquera persoa que chama escoita a gravación do contestador automático de principio a fin, pero podes colgar coa cancelación da subscrición.

Gustaríame sinalar que tamén hai os chamados regatos cálidos (coñecín esa definición moi raramente e só en comunidades estranxeiras): este é un fluxo que se transforma dun fluxo frío a outro quente. Xorde a pregunta: onde usar)) Vou poñer un exemplo da práctica.

Estou traballando con Angular. Usa activamente rxjs. Para obter datos ao servidor, espero un fluxo frío e uso este fluxo no modelo usando asyncPipe. Se uso este tubo varias veces, entón, volvendo á definición dun fluxo frío, cada tubo solicitará datos ao servidor, o que é o mínimo estraño. E se converto un fluxo frío nun quente, entón a solicitude ocorrerá unha vez.

En xeral, comprender o tipo de fluxos é bastante difícil para os principiantes, pero importante.

Operadores

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

Os operadores dannos a oportunidade de traballar con fluxos. Axudan a controlar os eventos que flúen no Observable. Consideraremos un par dos máis populares, e pódese atopar máis información sobre os operadores nas ligazóns en información útil.

Operadores-de

Imos comezar co operador auxiliar de. Crea un Observable baseado nun valor sinxelo.

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

Operadores-filtro

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

O operador de filtro, como o nome indica, filtra o sinal do fluxo. Se o operador devolve verdadeiro, salta máis.

Operadores - tomar

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

take - Toma o valor do número de emisións, despois do cal remata o fluxo.

Operadores-debonceTime

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

debounceTime: descarta os valores emitidos que se atopan dentro do intervalo de tempo especificado entre os datos de saída; despois de que o intervalo de tempo transcorre, emite o último 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ón asíncrona en JavaScript (Callback, Promise, RxJs)

Operadores-takeWhile

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

Emite valores ata que takeWhile devolve false e, a continuación, cancela a subscrición ao fío.

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ón asíncrona en JavaScript (Callback, Promise, RxJs)

Operadores-combineLatest

O operador combinado combineLatest é algo semellante a promise.all. Combina varios fluxos nun só. Despois de que cada fío realice polo menos unha emisión, obtemos os últimos valores de cada un como unha matriz. Ademais, despois de calquera emisión dos fluxos combinados, dará novos valores.

Programación 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ón asíncrona en JavaScript (Callback, Promise, RxJs)

Operadores-zip

Zip: agarda un valor de cada fluxo e forma unha matriz baseada nestes valores. Se o valor non procede de ningún fío, non se formará o grupo.

Programación 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ón asíncrona en JavaScript (Callback, Promise, RxJs)

Operadores - forkJoin

forkJoin tamén une os fíos, pero só emite un valor cando todos os fíos están completos.

Programación 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ón asíncrona en JavaScript (Callback, Promise, RxJs)

Operadores-mapa

O operador de transformación do mapa transforma o valor de emisión nun novo.

Programación 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ón asíncrona en JavaScript (Callback, Promise, RxJs)

Operadores: compartir, tocar

O operador de toque permítelle facer efectos secundarios, é dicir, calquera acción que non afecte á secuencia.

O operador de servizos públicos compartidos pode converter un fluxo frío nun quente.

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

Os operadores están feitos. Pasemos ao tema.

Pensando en voz alta

E entón fun tomar té. Estou farto destes exemplos 😀

Familia suxeito

A familia de temas é un excelente exemplo de fíos quentes. Estas clases son unha especie de híbrido que actúa como observable e observador ao mesmo tempo. Dado que o tema é un fluxo quente, debes cancelar a subscrición. Se falamos dos principais métodos, estes son:

  • seguinte: pasar novos datos ao fluxo
  • erro - erro e terminación do fío
  • completo - final do fío
  • subscribir - subscribirse a un fluxo
  • unsubscribe - cancelar a subscrición do fío
  • asObservable - transfórmase nun observador
  • toPromise - transfórmase nunha promesa

Asignar 4 5 tipos de materias.

Pensando en voz alta

Dixen 4 no stream, pero resultou que engadiron un máis. Como di o refrán, vive e aprende.

Tema sinxelo new Subject()- As materias máis sinxelas. Creado sen parámetros. Pasa os valores que só chegaron despois da subscrición.

ComportamentoSuxeito new BehaviorSubject( defaultData<T> ) - na miña opinión o tipo máis común de materia-s. A entrada toma o valor predeterminado. Garda sempre os datos do último número, que se transmite ao subscribirse. Esta clase tamén ten un método de valor útil que devolve o valor actual do fluxo.

ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number) - Opcionalmente, pode tomar como primeiro argumento o tamaño do búfer de valores que almacenará en si mesmo, e o segundo tempo durante o que necesitamos cambios.

suxeito asíncrono new AsyncSubject() - non ocorre nada ao subscribirse e o valor só se devolverá cando estea completado. Só se devolverá o último valor do fluxo.

WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>) - A documentación cala respecto diso e eu mesmo o vexo por primeira vez. Quen sabe o que fai, escribir, engadiremos.

Uf. Ben, consideramos todo o que quería contar hoxe. Espero que esta información fose útil. Podes ler a lista de literatura pola túa conta na pestana Información útil.

información útil

Fonte: www.habr.com

Engadir un comentario