Bienvenido, me llamo Luis y aquí les traigo un post.
Las promesas son simplemente envoltorios sobre unidades asíncronas de obras (u operaciones), se utilizan para representar valores futuros.
Una promesa puede estar en varios estados:
- Pendiente: esto significa que todavía está realizando la unidad de trabajo asincrónica.
- Cumplido: lo que significa que completó la unidad de trabajo asíncrona.
- Rechazado: lo que significa que falló en la ejecución (o durante la ejecución) de la unidad de trabajo asíncrona.
- Liquidado: lo que significa que la promesa se cumplió o se rechazó
Índice
Haciendo promesas
Creamos una promesa mediante el uso del objeto global Promise
como constructor y pasamos una función como argumento.
Esta función (llamémosla asyncUnitOfWork
) va a recibir dos funciones como argumentos, por lo que definimos dos parámetros formales llamados: fulfill
y rejected
.
Las promesas hacen énfasis en CPS, eso significa que no podemos interrumpir el flujo de control ni devolver valores de él usando return
.
En cambio, ejecutamos la función fulfill
para indicar que la promesa debe marcarse cumplida o ejecutarse reject
para indicar que la promesa debe marcarse como rechazada.
A tener en cuenta: cuando creamos una promesa de forma predeterminada, estará activada
pending
estado
Debido a que las promesas se refieren a unidades de trabajo asíncronas, simplemente agregamos un tiempo de espera de al menos 1000 milisegundos para dar la impresión de que esta promesa tarda en completarse.
Una vez el setTimeout
la devolución de llamada se ejecuta después de al menos 1000 milisegundos, la promesa se marca como fulfilled
. No estamos obligados a pasar nada al fulfill
función, pero en este caso porque nuestro asyncUnitOfWork
La función produce un valor futuro (el valor 10
) debemos pasar el valor al fulfill
continuación.
Si ejecutamos nuestro código notamos algunas cosas interesantes:
Primero registra "start"
en la consola, luego registra una promesa pendiente, luego registra "finish"
, luego espera al menos un segundo, y finalmente, devuelve el control de ejecución.
Pero nunca nos da nuestro valor futuro (o prometido) de 10
. Como dijimos anteriormente, las promesas son envolturas, no podemos interactuar directamente con ellas.
Afortunadamente para nosotros, las promesas nos brindan un mecanismo para interactuar con la promesa una vez que se establecen: then
método.
los then
El método toma dos funciones como argumentos: onFulfilled
y onRejected
.
La función onFulfilled
(como puede adivinar) se ejecutará cuando la promesa se haga fulfilled
. Y la función onRejected
se ejecutará cuando la promesa sea rejected
.
Observe cómo cambia el orden de los registros cuando ejecutamos esta versión del código.
Primero registra "start"
a la consola, luego inicia sesión "finish"
, luego «espera» al menos un segundo, luego registra "value: 10"
y finalmente, devuelve el control de ejecución.
Cuando se crea la promesa se envía al queue
(o la lista de cosas asincrónicas para hacer). Luego se ejecuta todo el código ejecutado en el marco de la pila (nuestro script). Una vez que la pila está vacía, nuestra unidad de trabajo asíncrona (nuestra promesa) se ejecuta y una vez que se fulfilled
(o rejected
) llama a la devolución de llamada correspondiente (onFulfilled
o onRejected
).
A tener en cuenta: Si no está familiarizado con el funcionamiento del bucle de eventos, Lee este artículo.
Rompiendo nuestras promesas
Nos hemos estado enfocando solo en fulfilled
promesas, ahora echemos un vistazo a rejected
unos.
Si queremos rechazar una promesa, solo necesitamos llamar al reject
función. Tal como el fulfill
función podemos pasar un argumento opcional a la función reject
. Generalmente, pasamos un error (o una razón) que describe por qué se rechazó esta promesa.
Si ejecutamos el código de arriba podemos ver que la función onFulfilled
no fue llamado, pero onRejected
estaba.
Llamar reject
no es la única forma de «marcar» una promesa como rechazada. Si lanzamos una función dentro de nuestras promesas (o dentro de cualquier función llamada dentro de la promesa) y no la manejamos a través de un try/catch
bloquear, la promesa se rechazará automáticamente.
En este caso en lugar de ejecutar reject
solo hacemos promesas. Algo a tener en cuenta es que la promesa será rechazada no solo por las excepciones que lanzamos, sino también por las que se lanzan en tiempo de ejecución (por ejemplo, al intentar hacer referencia a una variable que no existe).
Flujo secuencial de ejecución
Tener una promesa es aburrido, creemos una nueva promesa llamada promisedString
que intenta producir la cuerda "hello world"
.
Esta promesa «toma» al menos 500
milisegundos para producir su valor. Si ejecutamos este código veremos lo siguiente.
Primero, obtenemos el resultado de "hello"
y luego el resultado de 10
. Incluso cuando el promisedNumber
fue declarado primero y el promisedString
luego.
Las promesas no necesariamente se resuelven en el orden en que están escritas en el código.
Si queremos garantizar un orden secuencial de ejecución, debemos llamar al then
método de promisedString
dentro del then
método de promisedNumber
.
En el código anterior reorganizamos el código para que podamos llamar then
de promisedString
después then
de promisedNumber
.
promisedString
podría resolverse antes o después promisedNumber
pero no importa. Lo que garantiza el orden secuencial de ejecución no es cuándo se establece una promesa, sino cuándo then
se llama.
Puede parecer contrario a la intuición, pero funciona. Ahora vemos eso primero number: 10
está registrado en la consola y luego string: hello
.
Canalizaciones asincrónicas
Ahora que tenemos una ejecución asíncrona secuencial, podemos pensar en nuestra producción de número y luego de una cadena como una tubería, pero no como una tubería: una tubería asíncrona.
Así que hagamos que esta canalización sea reutilizable. Vamos a envolverlo dentro de una función llamada AsyncPipeline
, Lo sé, realmente creativa.
Pero, si ejecutamos este código, notaremos que aunque no estemos llamando a la función AsyncPipeline
imprime en consola value: 10
, entonces value: hello world
, luego espera al menos 1,5 segundos y devuelve el control de ejecución.
Estás comprometido hasta el final
Las promesas son impacientes (contrariamente a las perezosas), lo que significa que las promesas se cumplen hasta el final. Una vez que crea (o hace) una promesa, no puede retroceder: no puede cancelar una promesa.
En el código anterior declaramos y asignamos ambos promisedNumber
y promisedString
. Incluso si no estamos llamando a la función AsyncPipeline
estas dos funciones ya estaban programadas para su ejecución y de hecho se ejecutan llamamos then
o no.
Las promesas no tienen un mecanismo por sí mismas para diferir su ejecución, pero en JavaScript lo tenemos.
Aplazamiento de la ejecución
Aplazar la ejecución también se denomina producción de valor bajo demanda (o evaluación perezosa). Si tiene un valor, no lo calcula de inmediato, solo lo calcula cuando lo necesita. Pero, ¿Cómo hacemos algo tan mágico?
Usamos thunks.
Los thunks son solo funciones que, bueno, devuelven valores (como cualquier otra función).
Así que solo necesitamos dos funciones envolventes, promisedNumber
y promisedString
, con funciones.
Ahora con este cambio la ejecución de nuestras promesas se aplaza y si no llamamos a la función AsyncPipeline
nuestro programa solo imprime start
y finish
y devuelve inmediatamente el flujo de control de ejecución.
De vuelta en el infierno
Si miramos de cerca, podemos ver que nuestro código se está anidando cada vez más. Esto es lo que generalmente llamamos la pirámide de la fatalidad o devolución de llamada. A menudo oirá que usamos promesas para evitar la devolución de llamada, pero sin embargo aquí estamos.
Para comprender cómo deshacerse del infierno de devolución de llamada, primero debemos comprender cómo then
trabaja.
then
tiene dos firmas: la primera firma dice que toma dos funciones como argumentos (onFulfilled
y onRejected
), la primera función (onFulfilled
) toma un argumento de tipo a
y devuelve un valor de tipo b
. La segunda función toma un argumento de tipo c
y devuelve un valor de tipo d
. Y finalmente regresa una promesa de b
o d
.
A tener en cuenta: si no entiende de qué se trata tanto
a
,b
,c
yd
leer esto primero.
la segunda firma dice que también toma dos funciones como argumentos (onFulfilled
y onRejected
), la primera función (onFulfilled
) toma un argumento de tipo a
y devuelve un valor de tipo Promise b
. La segunda función toma un argumento de tipo c
y devuelve un valor de tipo Promise d
. Y finalmente regresa una promesa de Promise b
o Promise d
.
Esto significa que podemos devolver un valor o una promesa de onFulfilled
y recuperaremos un Promise
. Si lo que devuelve es una promesa, eso significa que podemos llamar then
encima de eso. En otras palabras: podemos encadenar nuestro then
llamadas.
Fíjate ahora cómo volvemos promisedString
desde promisedNumber#then
. Estamos devolviendo un Promise string
de eso y obteniendo lo mismo Promise string
como resultado. En este caso particular, usamos la segunda firma de then
.
Y con ese pequeño cambio, nos deshacemos del infierno de devolución de llamada.
Persiguiendo sombras
Ahora cambiemos un poco este código. promisedNumber
en lugar de producir una promesa cumplida con un valor, rechazará la promesa con un error.
Si ejecutamos este código en la consola obtenemos lo siguiente:
Conseguimos imprimir en la consola string: undefined
después de obtener el error de promisedNumber
. Extraño, ¿no?
En este caso promisedNumber
es rechazado, por lo tanto onFulfilled
no se llama. Y porque es onFulfilled
no se llama nunca devolvemos la promesa promisedString
. Entonces, ¿por qué se llama al próximo? ¿De dónde viene esa promesa?
Demos un paso atrás y recordemos la firma de then
.
Vemos que en ambos casos then
devuelve una promesa de b
o d
. b
es el valor onFulfilled
devuelve y d
es el valor onRejected
devoluciones.
No devolvemos nada en onRejected
de promisedNumber#then
. Pero eso no importa, si no devolvemos nada de una función, volverá automáticamente undefined
. Y cada vez que llamamos then
devolverá una nueva promesa.
Entonces, en este caso onRejected
de promisedNumber#then
está volviendo una nueva promesa de undefined
.
A tener en cuenta: Algo a tener en cuenta aquí es que no sólo podemos devolver nuevas promesas o valores de
then
ycatch
, también podemos devolver las promesas rechazadas de ellos.
Errores al tragar
Esto suena como un detalle insignificante, pero rompe completamente nuestro manejo de errores.
Si tenemos una canalización asincrónica de funciones que dependen una de la otra si el primer paso de nuestra canalización se rompe, no terminará (o truncará) la ejecución de las siguientes.
Peor aún si el paso actual depende del valor generado por el paso anterior.
Afortunadamente para su uso tenemos catch
.
catch
es un método al que podemos llamar sobre un rejected
prometido. Toma como argumento un onRejected
función. Si queremos usarlo debemos dejar de pasar una segunda función a then
y en lugar de pasar eso a catch
.
Si promisedNumber
es rechazado luego el siguiente then
la llamada no se ejecutará, pero catch
. Y ahora, si un paso de nuestra canalización «falla», la ejecución se trunca. Entonces podemos tener manejo de errores nuevamente.
Algo que quiero señalar es que ahora la firma de then
se ve un poco diferente.
En lugar de tomar dos funciones, ahora solo se necesita una, y en lugar de devolver una promesa de b
o d
ahora solo devuelve una promesa de b
.
Misc
Hay dos cosas de las que quiero hablar que no sabían cómo encajar en las secciones anteriores: Promise.resolve
y Promise.reject
.
Promise.resolve
solo nos permite crear un fulfilled
promesa y Promise.reject
nos permite crear un rejected
promesa.
Estas son solo funciones útiles, en lugar de escribir new Promise
y pasando una función que lleva resolve
y reject
simplemente llamamos a una de estas dos funciones.
Ambos Promise.resolve
y Promise.reject
tener el siguiente aspecto:
Ambos toman a
como argumento y ambos devuelven una promesa de a
. La única diferencia es el estado de la promesa.
El juego de imitación
Las mónadas asíncronas se utilizan para modelar el flujo de control asíncrono, al igual que las promesas.
Las mónadas tienen varios métodos interesantes. Solo quiero centrarme en tres de ellos:map
, flatMap
y of
.
map
se parece a lo siguiente:
Toma una función como argumento. La función toma a
como argumento y devoluciones b
. Finalmente map
devuelve una mónada asíncrona de b
.
flatMap
se parece a lo siguiente:
Toma una función como argumento. La función toma a
como argumento y devuelve una mónada asíncrona de b
. Finalmente flatMap
devuelve una mónada asíncrona de b
.
Y finalmente of
se parece a lo siguiente:
Se necesita un a
como argumento y devuelve una mónada asíncrona de a
.
Parece familiar, ¿no?
Eso es porque then
se comporta como ambos map
y flatMap
; y Promise.resolve
y Promise.reject
ambos se comportan como of
.
Las promesas se comportan (a veces) como mónadas, pero no son mónadas. Y la razón de eso es bastante tonta.
El infame hilo
Las promesas de JavaScript se basan en el estándar Promise / A +, que es solo una versión mejorada de Promise / A. Ambas fueron iniciativas impulsadas por la comunidad.
En ese entonces tuvo lugar una discusión: ¿debería la comunidad adoptar la teoría de categorías y las mónadas?
La discusión es … interesante, puedes encontrarla aquí. No tengo nada relevante que comentar al respecto, adelante, léelo y saca tus propias conclusiones.
Añadir comentario