Es el comienzo de un nuevo año, y aunque muchas personas prometen ser más activas, les mostraré cómo hacer Promise
para ser más vagos...Promise
s, eso es.
Tendrá más sentido en un momento.
Primero, veamos un ejemplo básico de Promise
. Aquí tengo una función llamada dormir que toma un tiempo en milisegundos y un valor. Devuelve una promesa que ejecutará un setTimeout
por la cantidad de milisegundos que debemos esperar; entonces la Promesa se resuelve con el valor.
/** * @template ValueType * @param {number} ms * @param {ValueType} value * @returns {Promise<ValueType>} */ function sleep(ms, value) { return new Promise((resolve) => { setTimeout(() => resolve(value), ms); }); }
Funciona así:
Podemos esperar la función de sleep
con los argumentos 1000
y 'Yawn & stretch'
, y después de un segundo, la console
registrará la cadena, 'Bostezo y estiramiento'.
No hay nada demasiado especial en eso. Probablemente se comporte como cabría esperar, pero se vuelve un poco extraño si lo almacenamos como una variable para await
más adelante, en lugar de await
la Promise
devuelta de inmediato.
const nap = sleep(1000, 'Yawn & stretch')
Ahora, digamos que hacemos otro trabajo que toma tiempo (como escribir el siguiente ejemplo), y luego await
la variable nap
.
Es posible que espere un retraso de un segundo antes de resolverse, pero de hecho, se resuelve de inmediato. Cada vez que crea una Promise
, crea una instancia de cualquier funcionalidad asíncrona de la que sea responsable.
En nuestro ejemplo, en el momento en que definimos la variable nap
, se crea Promise
que ejecuta setTimeout
. Como soy un tipeador lento, la Promise
se resolverá cuando la await
.
En otras palabras, los Promise
están ansiosos. Ellos no await
a que tú los esperes.
En algunos casos, esto es algo bueno. En otros casos, podría conducir a un uso innecesario de recursos. Para esos escenarios, es posible que desee algo que se parezca a una Promise
, pero use
Antes de continuar, quiero mostrarles algo interesante.
Las Promise
no son las únicas cosas que se pueden await
en JavaScript. Si creamos un Object
simple con un método .then()
, podemos await
ese objeto como cualquier Promise
.
Esto es un poco extraño, pero también nos permite crear diferentes objetos que se parecen a Promise
, pero no lo son. Estos objetos a veces se denominan “
Con eso en mente, vamos a crear un nuevoLazyPromise
que extiende el constructor Promise
integrado. Extender Promise no es estrictamente necesario, pero lo hace parecer más similar a Promise
usando cosas como instanceof
.
class LazyPromise extends Promise { /** @param {ConstructorParameters<PromiseConstructor>[0]} executor */ constructor(executor) { super(executor); if (typeof executor !== 'function') { throw new TypeError(`LazyPromise executor is not a function`); } this._executor = executor; } then() { this.promise = this.promise || new Promise(this._executor); return this.promise.then.apply(this.promise, arguments); } }
La parte en la que hay que centrarse es el método then()
. Se secuestra el comportamiento predeterminado de una Promise
estándar para esperar hasta que se ejecute el método .then()
antes de crear una Promise
real.
Esto evita instanciar la funcionalidad asincrónica hasta que realmente la solicite. Y funciona tanto si llamas explícitamente a .then()
como si usas await
.
Ahora, veamos qué sucede si reemplazamos Promise
en la función de sleep
original con LazyPromise
. Una vez más, asignaremos el resultado a una variable de nap
.
function sleep(ms, value) { return new LazyPromise((resolve) => { setTimeout(() => resolve(value), ms); }); } const nap = sleep(1000, 'Yawn & stretch')
Luego nos tomamos nuestro tiempo para escribir la línea await nap
y ejecutarla.
Esta vez, vemos un retraso de un segundo antes de que se resuelva la Promise
, independientemente del tiempo transcurrido desde que se creó la variable.
(Tenga en cuenta que esta implementación solo crea la nueva Promise
una vez y hace referencia a ella en llamadas posteriores. Por lo tanto, si tuviéramos que await
nuevamente, se resolvería inmediatamente como cualquier Promise
normal).
Por supuesto, este es un ejemplo trivial que probablemente no encontrará en el código de producción, pero hay muchos proyectos que usan objetos tipo Promise
con evaluación perezosa. Probablemente el ejemplo más común es con ORM de base de datos y generadores de consultas como
Considere el pseudocódigo a continuación. Está inspirado en algunos de estos generadores de consultas:
const query = db('user') .select('name') .limit(10) const users = await query
Creamos una consulta de base de datos que va a la tabla "user"
y selecciona las primeras diez entradas y devuelve sus nombres. En teoría, esto funcionaría bien con una Promise
normal.
Pero, ¿y si quisiéramos modificar la consulta en función de ciertas condiciones, como los parámetros de la cadena de consulta? Sería bueno poder continuar modificando la consulta antes de esperar finalmente la Promise
.
const query = db('user') .select('name') .limit(10) if (orderBy) { query.orderBy(orderBy) } if (limit) { query.limit(limit) } if (id) { query.where({ id: id }) } const users = await query
Si la consulta original de la base de datos fuera una Promise
estándar, instanciaría con entusiasmo la consulta tan pronto como asignáramos la variable, y no podríamos modificarla más adelante.
Con la evaluación perezosa, podemos escribir código como este que es más fácil de seguir, mejora la experiencia del desarrollador y solo ejecuta la consulta una vez cuando la necesitamos.
Ese es un ejemplo donde la evaluación perezosa es genial. También podría ser útil para cosas como crear, modificar y orquestar solicitudes HTTP.
Lazy Promise
s son geniales para los casos de uso correctos, pero eso no quiere decir que deban reemplazar cada Promise
. En algunos casos, es beneficioso instanciar con entusiasmo y tener la respuesta lista lo antes posible.
Este es otro de esos escenarios de "depende". Pero la próxima vez que alguien te pida que hagas una Promise
, considera ser perezoso al respecto ( ͡° ͜ʖ ͡°).
Muchas Gracias Por Leer. Si te ha gustado este artículo, por favor
Publicado originalmente en