paint-brush
Todo lo que necesita saber sobre las promesas, las posibilidades y la evaluación perezosapor@austingil
1,653 lecturas
1,653 lecturas

Todo lo que necesita saber sobre las promesas, las posibilidades y la evaluación perezosa

por Austin Gil5m2023/01/20
Read on Terminal Reader

Demasiado Largo; Para Leer

En el nuevo año, les mostraré cómo hacer que las `Promesas sean más perezosas. Veamos un ejemplo básico de 'Promesa'. 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.
featured image - Todo lo que necesita saber sobre las promesas, las posibilidades y la evaluación perezosa
Austin Gil HackerNoon profile picture

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... JavaScript 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í:


Consola JavaScript con el código, "await sleep(1000, 'Yawn & stretch')". Luego, después de un segundo, "'Bostezo y estiramiento'"


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 .


Escribiendo en la consola de JavaScript, "esperar siesta" e inmediatamente viendo la respuesta "'Bostezo y estiramiento'"

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 evaluación perezosa para instanciar solo cuando lo necesite.


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 .


Consola de JavaScript con el texto "await { then: () => console.log('🙃') }" seguido de "🙃".

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 “ entoncesables “.


Con eso en mente, vamos a crear un nuevo clase llamado LazyPromise 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.


Escribiendo en la consola de JavaScript, "esperar siesta" y después de un retraso de un segundo, viendo la respuesta "'Bostezo y estiramiento'"


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 Knex.js o prisma .


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 Compártelo . Es una de las mejores formas de apoyarme. Tú también puedes Inscríbete a mi boletín de noticias o Sigueme en Twitter si quieres saber cuando se publican nuevos artículos.


Publicado originalmente en austingil.com .