Hola a todos En contacto Omelnitsky Sergey. No hace mucho tiempo, dirigí una secuencia de programación reactiva donde hablé sobre asincronía en JavaScript. Hoy me gustaría esbozar este material.

Pero antes de comenzar el material principal, tenemos que hacer uno introductorio. Entonces, comencemos con las definiciones: ¿cuál es la pila y la cola?
Una pila es una colección cuyos elementos se reciben según el principio de LIFO "último en entrar, primero en salir"
Una cola es una colección cuyos elementos se reciben según el principio (FIFO "primero que entra, primero que sale")
Ok, continuemos.

JavaScript es un lenguaje de programación de subproceso único. Esto significa que solo tiene un hilo de ejecución y una pila en la que las funciones se ponen en cola para su ejecución. Por lo tanto, en un momento dado, JavaScript puede realizar solo una operación, mientras que otras operaciones esperarán su turno en la pila hasta que se llamen.
La pila de llamadas es una estructura de datos que, en términos simples, registra información sobre el lugar en el programa donde estamos. Si entramos en una función, colocamos un registro al respecto en la parte superior de la pila. Cuando volvemos de la función, sacamos el elemento superior de la pila y nos encontramos desde donde llamamos a esta función. Esto es todo lo que la pila puede hacer. Y ahora una pregunta extremadamente interesante. ¿Cómo funciona entonces la asincronía en JavasScript?

De hecho, además de la pila, los navegadores tienen una cola especial para trabajar con el llamado WebAPI. Las funciones de esta cola se ejecutarán en orden solo después de que la pila esté completamente limpia. Solo después de eso son empujados desde la cola a la pila para su ejecución. Si al menos un elemento está actualmente en la pila, entonces no pueden acceder a la pila. Es precisamente por esto que las funciones de llamada por tiempo de espera a menudo no son precisas en el tiempo, ya que una función no puede pasar de la cola a la pila mientras está llena.
Considere el siguiente ejemplo y realice su "ejecución" paso a paso. También vea lo que sucede en el sistema.
console.log('Hi'); setTimeout(function cb1() { console.log('cb1'); }, 5000); console.log('Bye');

1) Hasta ahora, no pasa nada. La consola del navegador está limpia, la pila de llamadas está vacía.

2) Luego se agrega el comando console.log ('Hola') a la pila de llamadas.

3) Y se ejecuta

4) Entonces console.log ('Hola') se elimina de la pila de llamadas.

5) Ahora vaya al comando setTimeout (función cb1 () {...}). Se agrega a la pila de llamadas.

6) Se ejecuta el comando setTimeout (función cb1 () {...}). El navegador crea un temporizador que forma parte de la API web. Él hará la cuenta regresiva.

7) El comando setTimeout (función cb1 () {...}) ha completado su trabajo y se elimina de la pila de llamadas.

8) El comando console.log ('Bye') se agrega a la pila de llamadas.

9) Se ejecuta el comando console.log ('Bye').

10) El comando console.log ('Bye') se elimina de la pila de llamadas.

11) Después de que hayan pasado al menos 5000 ms, el temporizador sale y coloca la devolución de llamada cb1 en la cola de devolución de llamada.

12) El bucle de eventos toma c la función cb1 de la cola de devolución de llamada y la coloca en la pila de llamadas.

13) La función cb1 se ejecuta y agrega console.log ('cb1') a la pila de llamadas.

14) Se ejecuta el comando console.log ('cb1').

15) El comando console.log ('cb1') se elimina de la pila de llamadas.

16) La función cb1 se elimina de la pila de llamadas.
Eche un vistazo a un ejemplo en dinámica:

Bueno, aquí hemos examinado cómo se implementa la asincronía en JavaScript. Ahora hablemos brevemente sobre la evolución del código asincrónico.
La evolución del código asincrónico.
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ón asincrónica, como la conocemos en JavaScript, solo se puede implementar con funciones. Se pueden pasar como cualquier otra variable a otras funciones. Así nacieron las devoluciones de llamada. Y es genial, divertido y provocativo, hasta que se convierte en tristeza, anhelo y tristeza. Por qué Sí, todo es simple:
- Con la creciente complejidad del código, el proyecto se convierte rápidamente en oscuros bloques anidados repetidamente: "infierno de devolución de llamada".
- El manejo de errores puede perderse fácilmente.
- No puede devolver expresiones con return.
Con Promise, las cosas mejoraron un poco.
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); });
- Aparecieron cadenas de promesa, que mejoraron la legibilidad del código
- Ha aparecido un método separado de captura de errores
- Ahora puedes correr en paralelo usando Promise.all
- Podemos resolver la asincronía anidada con async / await
Pero promis tiene sus limitaciones. Por ejemplo, una promesa, sin bailar con una pandereta, no se puede deshacer, y lo más importante, funciona con un valor.
Bueno, nos acercamos sin problemas a la programación reactiva. Estas cansado Bueno, lo bueno es que puedes hacer algunas gaviotas, pensar y volver a leer más. Y continuaré.

La programación reactiva es un paradigma de programación centrado en los flujos de datos y la difusión del cambio. Echemos un vistazo más de cerca a lo que es un flujo de datos.
Imagine que tenemos un campo de entrada. Creamos una matriz, y para cada keyup del evento de entrada guardaremos el evento en nuestra matriz. En este caso, me gustaría señalar que nuestra matriz está ordenada por tiempo, es decir El índice de eventos posteriores es mayor que el índice de eventos anteriores. Tal matriz es un modelo de flujo de datos simplificado, pero aún no es un flujo. Para que esta matriz se pueda llamar de forma segura una secuencia, debe poder informar de alguna manera a los suscriptores que ha recibido nuevos datos. Entonces llegamos a la definición de flujo.
Flujo de datos
const { interval } = Rx; const { take } = RxOperators; interval(1000).pipe( take(4) )

Una secuencia es una matriz de datos ordenados por tiempo que puede indicar que los datos han cambiado. Ahora imagine lo conveniente que resulta escribir código en el que necesita activar varios eventos en diferentes partes del código en una sola acción. Simplemente nos suscribimos a la transmisión y él nos avisará cuando ocurran los cambios. Y la biblioteca RxJs puede hacer esto.

RxJS es una biblioteca para trabajar con programas asincrónicos y basados en eventos que utilizan secuencias observables. La biblioteca proporciona el tipo principal de Observable , varios tipos auxiliares ( Observador, Programadores, Temas ) y operadores para trabajar con eventos como con colecciones ( mapa, filtro, reducción, todos y similares de JavaScript Array).
Veamos los conceptos básicos de esta biblioteca.
Observable, observador, productor
Observable es el primer tipo básico que veremos. Esta clase contiene la mayor parte de la implementación de RxJs. Está asociado con una secuencia observable, a la que ambos pueden suscribirse utilizando el método de suscripción.
Observable implementa un mecanismo auxiliar para crear actualizaciones, el denominado Observador . La fuente de valores para Observer se llama Producer . Puede ser una matriz, un iterador, un socket web, algún tipo de evento, etc. Entonces podemos decir que observable es un conductor entre Productor y Observador.
Observable maneja tres tipos de eventos de observador:
- siguiente - nuevos datos
- error: un error si la secuencia terminó debido a una excepción. Este evento también implica la finalización de la secuencia.
- complete - señal sobre la finalización de la secuencia. Esto significa que no habrá nuevos datos.
Veamos la demo:

Al principio procesaremos los valores 1, 2, 3 y después de 1 seg. obtendremos 4 y terminaremos nuestro flujo.
Pensamientos en el oídoY luego me di cuenta de que contar era más interesante que escribir al respecto. : D
Suscripción
Cuando nos suscribimos a una transmisión, creamos una nueva clase de suscripción que nos permite cancelar la suscripción utilizando el método de cancelación de suscripción . También podemos agrupar suscripciones utilizando el método add . Bueno, es lógico que podamos desagrupar los hilos con eliminar . Los métodos de entrada de agregar y quitar aceptan otra suscripción. Me gustaría señalar que cuando nos damos de baja, nos damos de baja de todas las suscripciones secundarias como si llamaran el método para darse de baja. Adelante
Tipos de corrientes
Para dar una analogía, me imagino una transmisión en caliente como una película en una sala de cine. En qué momento viniste, desde ese momento y comenzaste a ver. Yo compararía una corriente fría con una llamada en esos. apoyo Cada persona que llama escucha el contestador automático de principio a fin, pero puede colgar sin darse de baja.
Me gustaría señalar que todavía existen los llamados flujos cálidos (una definición que conocí muy raramente y solo en comunidades extranjeras): esta es una corriente que se transforma de una corriente fría a una caliente. Surge la pregunta: dónde usar)) Daré un ejemplo de la práctica.
Yo trabajo con un angular. Él usa activamente rxjs. Para llevar datos al servidor, espero una transmisión en frío y la uso en la plantilla usando asyncPipe. Si uso esta tubería varias veces, luego, volviendo a la definición de una transmisión en frío, cada tubería solicitará datos del servidor, lo cual es extraño por decir lo menos. Y si convierto una corriente fría en una cálida, la solicitud sucederá una vez.
En general, comprender la forma de los flujos es bastante complicado para los 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) );
Los operadores nos brindan la capacidad de trabajar con transmisiones. Ayudan a controlar los eventos que ocurren en el Observable. Consideraremos algunos de los más populares, y los operadores se pueden encontrar con más detalle utilizando los enlaces en la información útil.
Operadores - de
Comenzamos con el operador auxiliar de. Crea un Observable basado en un valor simple.

Operadores - filtro

El filtro operador de filtro, como su nombre lo indica, filtra la señal de flujo. Si el operador devuelve verdadero, salta aún más.
Operadores - tomar

take: toma el valor del número de emisiones, después de lo cual finaliza la secuencia.
Operadores - debounceTime

debounceTime: descarta los valores emitidos que caen dentro del período de tiempo especificado entre los datos de salida, después del lapso del intervalo de tiempo que emite el último valor.
const { Observable } = Rx; const { debounceTime, take } = RxOperators; Observable.create((observer) => { let i = 1; observer.next(i++);

Operadores - takeWhile

Emite valores hasta que takeWhile devuelve falso, después de lo cual se dará de baja de la secuencia.
const { Observable } = Rx; const { debounceTime, takeWhile } = RxOperators; Observable.create((observer) => { let i = 1; observer.next(i++);

Operadores - combineLatest
El operador combine combineLatest es algo similar a promise.all. Combina varios hilos en uno. Después de que cada hilo hace al menos una emisión, obtenemos los últimos valores de cada uno en forma de matriz. Además, después de cualquier emisión de los flujos combinados, dará nuevos valores.

const { combineLatest, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1;

Operadores - zip
Zip: espera un valor de cada flujo y forma una matriz basada en estos valores. Si el valor no proviene de ninguna secuencia, el grupo no se formará.

const { zip, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1;

Operadores - forkJoin
forkJoin también concatena hilos, pero solo valora cuando todos los hilos están completos.

const { forkJoin, Observable } = Rx; const { take } = RxOperators; const observer_1 = Observable.create((observer) => { let i = 1;

Operadores - mapa
El operador de transformación del mapa convierte el valor de emisión en uno nuevo.

const { Observable } = Rx; const { take, map } = RxOperators; Observable.create((observer) => { let i = 1;

Operadores: compartir, tocar
El operador de toque: le permite realizar efectos secundarios, es decir, cualquier acción que no afecte la secuencia.
El operador de utilidad compartida puede calentarlo desde una corriente fría.

Con los operadores terminados. Pasemos al tema.
Pensamientos en el oídoY luego fui a tomar unas gaviotas. Estos ejemplos me aburren: D
Familia sujeta
La familia de sujetos es un excelente ejemplo de corrientes calientes. Estas clases son un tipo de híbrido que actúa simultáneamente como observable y observador. Dado que el asunto es una transmisión en caliente, debe cancelar su suscripción. Si hablamos de los métodos básicos, entonces esto:
- siguiente: transferencia de nuevos datos a la secuencia
- error - error y terminación de la transmisión
- complete - terminación de la transmisión
- suscribirse - suscribirse a la transmisión
- cancelar suscripción - cancelar suscripción de la transmisión
- asObservable - transformarse en un observador
- toPromise - se transforma en una promesa
Asignar 4 4 5 tipos de asignaturas.
Pensamientos en el oídoÉl habló en la secuencia 4, pero resultó que agregaron uno más. Como dice el refrán, vive y aprende.
Asunto simple new Subject()
es el tipo de sujeto más simple. Se crea sin parámetros. Pasa valores que llegaron solo después de la suscripción.
BehaviorSubject new BehaviorSubject( defaultData<T> )
: en mi opinión, el tipo de tema más común. La entrada acepta un valor predeterminado. Siempre guarda los datos de la última emisión, que transfiere durante la suscripción. Esta clase también tiene un método de valor útil que devuelve el valor actual de la secuencia.
ReplaySubject new ReplaySubject(bufferSize?: number, windowTime?: number)
: la entrada puede aceptar opcionalmente el primer argumento como el tamaño del búfer de valores que almacenará en sí mismo, y la segunda vez durante la cual necesitamos cambios.
AsyncSubject new AsyncSubject()
: no ocurre nada al suscribirse y el valor se devolverá solo cuando se complete. Solo se devolverá el último valor de transmisión.
WebSocketSubject new WebSocketSubject(urlConfigOrSource: string | WebSocketSubjectConfig<T> | Observable<T>, destination?: Observer<T>)
- La documentación no dice nada al respecto y la veo por primera vez. Quién sabe lo que está haciendo, escribe, complementa.
Fuf Bueno, aquí hemos considerado todo lo que quería contar hoy. Espero que esta información haya sido útil. Puede familiarizarse con la lista de referencias en la pestaña de información útil.