Función para esperar algo

por Halí - publicado: Octubre 09, 2021
JavaScriptFunciones

En este artículo quiero mostrarte algunas cosas acerca del lenguaje de programación JavaScript mediante el uso de una función que servirá para «esperar».

Contenido

Muchas veces cuando interactuamos con una API, tenemos que esperar un cambio de estado, por ejemplo cuando queremos instanciar un servidor y estamos esperando a que esté listo. O cuando estamos preguntando por la ubicación de un usuario.

Funciones de alto orden

Si sabes que son las funciones de alto orden, puedes saltarte esta parte

Las funciones de alto orden son por definición funciones que reciben como parámetro funciones o que devuelven funciones.

Aquí hay algunos ejemplos de funciones de alto orden:

Funciones como parámetro

const fun = (callback) => {
  const value = 3
  callback(value)
}

fun((val) => {
  console.log(val) // 3
})

Funciones como valor de retorno

const fun = (param) => {
  return () => {
    console.log(param)
  }
}

const log = fun(3)

log() // 3

Tienen usos específicos y particulares, en este caso usaremos el primer tipo, las que reciben funciones cómo parámetro.

Repetición

En cualquier caso (consumiendo una API) necesitamos esperar pero debemos hacerlo mientras preguntamos de vez en vez, particularmente cuando no hay un mecanismo de webhook que nos avise o notifique del cambio de estado.

Así, podemos hacer llamadas cada cierto tiempo, con setInterval, de esta forma:

setInterval(() => {
  APICall() // Llamada a la API
}, waitTime) // Tiempo a esperar

Ahora bien, cuando el resultado es el deseado, debemos detener la repetición de llamadas, de esta forma:

const interval = setInterval(() => {
  APICall().then(() => {
    // Condición de parada
    if (condition) {
      clearInterval(interval) // Detener la repetición
    }
  })
}, waitTime)

Ahora si queremos devolver un valor, podemos envolver este código en una Promesa.

Promesas

Si sabes que son las promesas, puedes saltarte esta parte

Las promesas son objetos que representan el resultado exitoso o fallido de una operación asíncrona.

Una promesa se construye de manera sencilla, solamente recibe una función que tiene dos parámetros, resolve y reject (ambos funciones) que renombraremos para mayor facilidad resolve -> good, reject -> bad, así:

new Promise((good, bad) => {})

En el cuerpo de la función, podemos llamar libremente a estas dos funciones, good cuando el resultado es exitoso y bad cuando es fallido. Algo muy interesante de las promesas es que pueden ser llamadas en una función asíncrona, o no.

Para obtener el valor resultado de una promesa, contamos con el método then, y el método catch.

then recibe una función con un parámetro que es el valor que le pasamos al método good.

catch recibe una función con un parámetro que es el valor que le pasamos al método bad.

new Promise((good, bad) => {
  good("Nice")
}).then((param) => {
  console.log(param) // Nice
})

new Promise((good, bad) => {
  bad("Not nice")
}).catch((param) => {
  console.log(param) // Not nice
})

Pero podemos usar await si usamos la promesa dentro de una función asíncrona. El costo extra que tenemos que pagar es que para el catch, debemos usar un bloque try...catch.

const nice = await new Promise((good, bad) => {
  good("Nice")
})

try {
  await new Promise((good, bad) => {
    bad("Error")
  })
} catch (error) {
  console.log(error) // Error
}

Así, podemos usar las promesas para la función que «espera».

const value = await new Promise((good) => {
  const interval = setInterval(() => {
    APICall().then(() => {
      if (condition) {
        clearInterval(interval)
        return good("Nice") // Devolver algún valor
      }
    })
  }, waitTime)
})

console.log(value) // Nice

Por último para reutilizar esta función y no tener que escribirla en todos lados, podemos usar más funciones de alto orden.

Resultado

El resultado final es algo así:

function waitFor({ condition, callback, message = "", waitTime = 1000 } = {}) {
  return new Promise((resolve) => {
    if (condition()) return resolve(condition())
    if (message.length) console.log(message)

    const interval = setInterval(() => {
      const stop = (result) => {
        clearInterval(interval)
        return resolve(result)
      }

      callback(stop)
    }, waitTime)
  })
}

Ahora procedo a explicar la función.

Como parámetros tenemos dos funciones, una de condición y una de callback o llamada atrás, un mensaje que se mostrará en la primera iteración y un tiempo de espera.

Devolvemos la promesa en la que solo necesitamos el parámetro resolve (que llamabamos good), en la primera línea de la promesa, comprobamos que la condición se cumpla y de ser así devolvemos ese valor.

En caso contrario continuamos y procedemos a imprimir el mensaje (en caso de que exista), y luego continuamos con las repeticiones.

En la repetición, creamos la función stop para detener las repeticiones y devolver el valor necesario, y por último llamamos a la función callback con la función stop como parámetro.

Uso

Implementé un servidor de prueba para regresar un valor numérico aleatorio entre el 1 y el 10, y la siguiente función hace llamadas hasta encontrar exactamente el número 6.

import axios from "axios"
import { waitFor } from "./src/waitFor.js"

const fun = async () => {
  const url = "http://localhost:3001/number"
  const {
    data: { number },
  } = await axios.get(url)
  console.log({ number })

  const result = await waitFor({
    message: "Waiting...",
    condition: () => (number === 6 ? { number } : false),
    callback: (stop) => {
      axios.get(url).then(({ data: { number } }) => {
        if (number === 6) stop({ number })
        console.log({ number })
      })
    },
  })
}

fun()

Pero es mejor un vídeo para mostrar cómo funciona.

En fin, si tienes dudas o comentarios, puedes enviarme un mensaje.

Adiós 👋🏽