hand Inicio

SERVICIOS

Salir

nochedía

DESARROLLO WEB con
GATSBY y WORDPRESS

emoji Cursos [24]
emoji Themes Gatsby [5]
emoji Themes WordPress [2]
emoji Blog [83]
emoji Herramientas [11]

Combinando promesas (await async) con loops en JavaScript

400 palabras
2 minutos
July 3, 2020
blogjavascript

Los loops `forEach` y `map` no funcionan como los clásicos `for`, con lo que su uso con `await` y `async` se complica

  1. Promesas con await y async
  2. Y cuando estamos en un loop qué, funciona igual?
  3. Loop con reduce

Promesas con await y async

Las promesas en JavaScript fueron un gran qué, pero eran y son un pequeño caos

El objetivo? Conseguir una manera de no bloquear la ejecución del código mientras una instrucción aún no se había completado

  • Por ejemplo, leer datos de una fuente externa

Cómo funcionan las promesas? Nos devuelven una promise y una manera de saber cuándo esa promesa se resuelve

El concepto es perfecto, pero en código la implementación no fue brillante

Entonces vino el async y el await, una manera de expresar promesas que sí es brillante

Por ejemplo, vamos a suponer que aquí la función fetchMyData nos devuelve una promesa

js
const fetching = () => {
const data = fetchMyData()
console.log(data) // me sale "pending"
}
fetching()

En este código necesitamos esperar a que data exista (es decir a que la promesa que nos devuelve fetchMyData se resuelva)

Pues tan sencillo como esto

js
const fetching = async () => {
const data = await fetchMyData()
console.log(data) // data es lo que sea que fuere data
}
fetching()

Y cuando estamos en un loop qué, funciona igual?

Para los loops "clásicos" la respuesta era sí, funcionan igual

Por ejemplo con un for..of

js
// función que implementa un wait con promesas
const wait = ms => new Promise((r, j) => setTimeout(r, ms))
// función que emula un proceso asíncrono
const fetchUrl = async url => {
await wait(1000)
return 'fantastic ' + url
}
// aquí vamos a hacer ver que pedimos 3 webs, y lo hacemos con un loop for .. of
const fetching = async () => {
const urls = ['https://www.site1.com', 'https://www.site2.com', 'https://www.site3.com']
for (const url of urls) {
const data = await fetchUrl(url)
console.log(data)
}
}
fetching()
Ver en CodeSandBox

En este loop las cosas funcionan como queremos, un console.log a cada segundo, fabuloso

Pero y si queremos un loop menos declarativo y más funcional?

Por loops funcionales tenemos el forEach y el map, y estos loops no funcionan como esperaríamos

La diferencia entre un forEach y un map es que el segundo nos devuelve un valor, y el primero no

Vamos a verlo con un forEach, y verás que he puesto el async dentro de la función forEach, eso ya te da una pista de lo que está ocurriendo

js
const wait = ms => new Promise((r, j) => setTimeout(r, ms))
const fetchUrl = async url => {
await wait(1000)
return 'fantastic ' + url
}
const fetching = () => {
const urls = ['https://www.site1.com', 'https://www.site2.com', 'https://www.site3.com']
urls.forEach(async url => {
const data = await fetchUrl(url)
console.log(data)
})
}
fetching()
Ver en CodeSandBox

Funciona como esperaríamos?

No

Tenemos los console.log que aparecen al instante y no a cada segundo

Por qué?

Porque todos los console.log se ejecutan a la vez

Por qué?

forEach y map ejecutan una función que recibe como argumento el elemento del array

js
Array.forEach((argumentos) => {})

Bien, pues el problema es que esas funciones las ejecuta en paralelo, no podemos decirle que se espere

Y además, tampoco podemos bloquear el código de todo el forEach completo

Lo puedes comprobar en este ejemplo

js
const wait = ms => new Promise((r, j) => setTimeout(r, ms))
const fetchUrl = async url => {
await wait(5000) // ahora son 5 segundos
return 'fantastic ' + url
}
const fetching = () => {
const urls = ['https://www.site1.com', 'https://www.site2.com', 'https://www.site3.com']
urls.forEach(async url => {
const data = await fetchUrl(url)
console.log(data)
})
console.log('hemos acabado') // sale al principio!
}
fetching()
Ver en CodeSandBox

Aquí el mensaje hemos acabado nos aparece inmediatamente, y a los 5 segundos nos aparecen los tres fantastic ... a la vez

Es decir, aquí no se bloquea nada

Una opción podría ser ponerle un await delante del forEach, pero tampoco funciona puesto que el forEach no devuelve ninguna promesa

Si en lugar de un forEach utilizamos map, éste nos devuelve una promesa por cada elemento, y podríamos hacer lo siguiente

js
const fetching = async () => {
const urls = ['https://www.site1.com', 'https://www.site2.com', 'https://www.site3.com']
const promesas = urls.map(async url => {
const data = await fetchUrl(url)
})
await Promise.all(promesas)
console.log('hemos acabado')
}
fetching()

Así conseguimos bloquear el código hasta que termine todo el map, pero no podemos bloquear cada iteración del loop (ocurren en paralelo)

Cómo podemos hacerlo?

Con reduce (tienes una entrada aquí)

Loop con reduce

js
const wait = ms => new Promise((r, j) => setTimeout(r, ms))
const fetchUrl = async url => {
await wait(1000)
return 'fantastic ' + url
}
const fetching = async () => {
const urls = ['https://www.site1.com', 'https://www.site2.com', 'https://www.site3.com']
await urls.reduce(async (previousPromise, url) => {
await previousPromise
const data = await fetchUrl(url)
console.log(data)
return Promise.resolve()
}, Promise.resolve())
console.log('hemos acabado')
}
fetching()
Ver en CodeSandBox

reduce nos permite incrustar una función en cada ciclo del loop, por lo que podemos implementar una promesa y esperar a que se resuelva, y así bloquear el código como lo hacíamos en el for..of anterior

Y ya lo tenemos

Dicho esto, mejor con reduce o mejor con for..of?

Depende de tu caso

  • Bien podrías definir una función global con reduce, refactorizarla y reutilizarla y así poder disfrutar del paradigma funcional

  • O podrías utilizar for..of y quedarte tan contento, no hay nada malo en mezclar paradigmas

🙋‍♂️

Qué tal la entrada?
👌 Bien 🙌🙌
👍 Bien, pero algunas cosas podrían explicarse mejor 😬
🤷‍♂️ Da por sentadas demasiadas cosas 😒
🤷‍♂️ A ver, hay poca chicha 😬
🤷‍♂️ Los ejemplos no son muy claros 🙇‍♂️
🤷‍♂️ No se entiende, está mal escrito 👎
✍️ Hay errores, revísalo en cuanto puedas 🙏

Más entradas

Privacidad
by kuworking.com
[ 2020 >> kuworking ]