Muy buenas, les saluda Luis y aquí les traigo otro tutorial.
Las promesas son quizás lo más complicado y frustrante en el mundo de Javascript (sin tener en cuenta el Javascript en sí).
Si bien el concepto en realidad no es tan difícil de comprender, las cosas asincrónicas pueden hacer que incluso los programadores experimentados reciten su vocabulario prohibido desde tercer grado.
Los desafíos más difíciles al trabajar con Promises son los siguientes:
- ¿Cómo puedo ejecutar una serie de promesas y luego obtener su resultado acumulativo cuando todas estén terminadas?
- ¿Cómo puedo ejecutar una serie de promesas en una secuencia lenta, lo que significa que una solo comenzará a ejecutarse cuando la anterior se haya completado por completo?
Ambos son posibles, aunque el último no es para lo que generalmente están diseñadas las Promesas. Pero en una situación en la que potencialmente hay miles de promesas a la mano (por ejemplo, cuando tiene que revisar una lista de URL y guardar las respuestas localmente) el procesamiento paralelo puede no ser una muy buena idea.
Construyamos una promesa simple
Quizás la forma más básica de Promise
, usado frecuentemente para ilustrar el concepto en tutoriales, está esperando unasetTimeout()
completar:
const delay = new Promise(resolve =>{ setTimeout(() => { resolve(); }, 5000); }).then(() => { console.log('Done waiting.'); });
Tengamos una matriz de números, cada uno de los cuales indica una cantidad de milisegundos de espera. Vamos a llamar a la función de tiempo de espera anterior con cada uno.
const delays = [1000, 2000, 5000, 3000, 500, 12000];
Primero lo primero, actualice nuestro Promise
para aceptar un parámetro para el valor de milisegundos. Tendremos que convertirlo en una función.
const delay = (milliseconds) => { return new Promise(resolve => { setTimeout(() => { resolve(milliseconds); , milliseconds); }); } delay(12000) .then(milliseconds => { console.log(`Done waiting $milliseconds / 1000 seconds.`); });
¿Que ha cambiado? Envolvemos el Promise
en una función. Así es como podemos pasarle un parámetro.
También cambiamos el resolve()
llamar para devolver el valor de milisegundos, por lo que nuestro .then()
branch puede decirnos cuánta espera se acaba de completar.
El código anterior llama delay
con un parámetro de 12.000
. Nuestro script esperará 12.000
milisegundos y luego dirá:
Done waiting 12 seconds.
Previamente definimos delay
como un Promise
. Ahora es un function
, pero como está regresando un Promise
, todavía podemos tratarlo como un Promise
.
Entrar en la matriz
Recuerde, tenemos esta pequeña matriz por ahí:
const delays = [1000, 2000, 5000, 3000, 500, 12000];
Queremos llamar al delay
función con cada valor, y esperamos que nuestro script espere 1 segundo, luego 2
segundos, luego 5
, 3
, 0.5
y finalmente 12
. En total, serán 23.5
segundos.
Nuestra cadena de promesas será como un espectáculo de fuegos artificiales. No queremos que suba todo a la vez. (De hecho, esto es lo que sucedió en San Diego en 2012, y fue muy espectacular, pero todavía no es lo que queremos que suceda).
La ejecución por lotes o una cadena de promesas es posible con el Promise.all()
método. Es una pequeña función útil que le permite pasar una matriz de Promise
objetos para ejecutarlos y darle un .then()
y .catch()
manipulador. Todo lo que tiene que hacer es recopilar sus llamadas en una matriz.
¿Cómo? Con el array.map()
método.
Promise.all( delays.map(d => delay(d)) ) .then(() => console.log('Waited enough!'));
Ahora podemos detectar cuándo terminaron de ejecutarse todas nuestras Promesas.
Sin embargo, esto los activará todos a la vez. Nuestro script en realidad solo esperará 12
segundos, ya que el valor más grande en nuestra matriz es 12,000
, y cuando finaliza, todos los demás también han terminado. Así que nuestros fuegos artificiales subieron todos a la vez.
En realidad, esto puede no ser un problema en la práctica. Los motores de Javascript son lo suficientemente inteligentes como para notar que se está formando una cola, y aunque todavía aceptarán sus promesas, en realidad no funcionan más de lo que los recursos permiten.
Entonces, si pasa diez mil solicitudes de URL, no todas se ejecutarán instantáneamente. Sin embargo, si su cadena de promesas es lo suficientemente larga, puede causar una TooManyElementsInPromiseAll
excepción. De acuerdo a Pruebas de la unidad V8, necesita 2²¹
o 2097151
promesas simultáneas.
Déjame mencionar rápidamente que Promise.all()
irá a su manejador .catch()
si solo un Promise
en la cadena ha fallado.
Puede considerar usar Promise.allSettled()
en su lugar, que generará un JSON ordenado en su rama .then()
que le dice qué valor de entrada terminó el Promise
con éxito y cuál no. Atención: no parece funcionar en TypeScript 3.9.7
.
[ status: 'fulfilled', value: 1000 , status: 'rejected', reason: '2000, I hate this number' , status: 'fulfilled', value: 5000 , status: 'fulfilled', value: 3000 , status: 'fulfilled', value: 500 , status: 'rejected', reason: '2000, I hate this number' ]
Verdadera ejecución secuencial
Pero realmente, ¿cómo podemos hacer que el ciclo ejecute las Promesas una por una, en lugar de todas a la vez?
Una forma es hacerlo manualmente:
delay(delays[0]) .then(() => delay(delays[1])) .then(() => delay(delays[2])) .then(() => delay(delays[3])) .then(() => delay(delays[4])) .then(() => delay(delays[5])) .then(() => console.log('Done!'));
Esto funciona, pero no es lo que queremos hacer, excepto quizás cuando tenemos un conjunto de valores codificados y tampoco nos importa la expresión facial de nuestro líder tecnológico.
Desafortunadamente, Promise
es completamente asincrónico y no se puede hacer que actúe de forma sincrónica. ¡Excepto, por supuesto, si no nos importa un poco de piratería! La solución es crear una función recursiva. Aquí está el código completo:
const delay = (milliseconds) => { console.log(`Waiting: $milliseconds / 1000 seconds.`); return new Promise((resolve) => { setTimeout(() => { resolve(milliseconds); }, milliseconds); }); } const delays = [1000, 2000, 5000, 3000, 500, 12000]; const startTime = Date.now(); const doNextPromise = (d) => { delay(delays[d]) .then(x => console.log(`Waited: $x / 1000 secondsn`); d++; if (d < delays.length) doNextPromise(d) else console.log(`Total: $(Date.now() - startTime) / 1000 seconds.`); ) doNextPromise(0);
Nuestra delay()
la función no ha cambiado, y tampoco nuestra delays
formación. La magia comienza en la función doNextPromise()
. Es una función recursiva que toma un número y llama a la función delay()
, pasando delays[d]
.
Entonces en la rama .then()
que incrementamos d
y llama doNextPromise()
de nuevo, hasta d
es menor que el último índice de la formación delays
:delays.length-1
.
Cuando d
es mayor que el último índice, le decimos al usuario cuánto tiempo ha pasado desde que comenzamos el ciclo.
Ejecute este script y verá algo como esto:
Waiting: 1 seconds. Waited: 1 secondsWaiting: 2 seconds. Waited: 2 secondsWaiting: 5 seconds. Waited: 5 secondsWaiting: 3 seconds. Waited: 3 secondsWaiting: 0.5 seconds. Waited: 0.5 secondsWaiting: 12 seconds. Waited: 12 secondsThis loop took 23 seconds.
Buena suerte desenredando Promise
. Gracias por leer este post.
Añadir comentario