Articles by Sergio Xalambrí

Clase rápida de programación asíncrona

El código síncrono se ejecuta una linea a la vez, cierto? Entonces si haces esto:

let result = fn()
console.log(result)

Vas a tener en tu console el resultado de llamar a fn, esto es porque fn es una función síncrona, así que cuando se ejecuta, se ejecuta todo el código dentro de fn, cuando termina su resultado se guarda en result y tu código sigue normal.

El código asíncrono no hace esto, porque justamente es asíncrono.

Entonces si fn ahora es una función asíncrona y haces lo mismo que antes (let result = fn()) el valor en result no va a ser el resultado de fn, si no una promesa.

Obtener una promesas significa "no tengo el resultado todavía, pero cuando lo tenga vas a poder enterarte", como te enterás? haciendo .then a la promesa.

let resultPromise = fn()
resultPromise.then(result => {
  // acá tenés el resultado de la promesa
})

Si haces un console.log fuera del .then no vas a poder obtener el resultado, solo vas a obtener la promesa.

let resultPromise = fn()
resultPromise.then(result => {
  console.log(resultPromise) // este log se ejecuta segundo, con la promesa completa
  console.log(result) // este log se ejecuta tercero, con el resultado de la promesa
})
console.log(resultPromise) // este log se ejecuta primero, con la promesa pendiente

En el momento en que haces algo asíncrono ya no podés seguir con un flujo síncrono normal, tu código que dependa del resultado de la promesa, tiene que estar dentro del .then.

Alternativamente, podés usar async/await para trabajar con código asíncrono de forma más sencilla:

let result = await fn()

Al poner await delante de fn() lo que haces es decirle al runtime de JS "espera acá hasta que la promesa que devuelve fn se complete y después seguí" que es básicamente lo mismo que hacer:

fn().then(result => /* algo acá */)

Y es lo mismo que hacer

let resultPromise = fn() // obtenemos la promesa
let result = await resultPromise // esperamos a que se complete la promesa

Ahora, un componente de React no es y no puede ser asíncrono, por lo que si querés ejecutar una función que devuelve una promesa y usar el resultado en el JSX que renderiza tu componente necesitas mover esa promesa a un useEffect y guardar el resultado en un estado:

let [result, setResult] = useState()
useEffect(function getResult() {
  // con esto llamamos a `fn`, esperamos a que se complete la promesa y guardamos el resultado en el estado que creamos arriba
  fn().then(fnResult => setResult(fnResult))
}, [])
// si result es undefined mostras un mensaje de carga, un spinner o algo
if (!result) return <p>Loading results...</p>
// cuando result esté definido lo mostras
return <p>{result}</p>

Lo importante acá es que tu componente se va a tener que renderizar dos veces:

  1. Se renderiza la primera vez, el estado result es undefined, se ejecuta el efecto y se renderiza el <p>Loading results...</p>
  2. fn se completa, se actualiza el estado, esto causa un nuevo render, en este nuevo render result tiene un valor (lo que devuelve fn), el efecto no se vuelve a ejecutar y se renderiza el <p>{result}</p>

Entonces lo importante es que siempre que quieras obtener el resultado de una función asíncrona vas a obtener una promesa, y para obtener el resultado de esa promesa tenés que hacer fn().then(result => { }) o hacer let result = await fn(), nunca deberías hacer let result = fn(); console.log(result) porque la función asíncrona no va a estar completa y el resultado no va a estar disponible.