Escrito por Rosario De Chiara✏️
En JavaScript, las promesas son una poderosa herramienta para manejar operaciones asincrónicas, particularmente útiles en eventos relacionados con la interfaz de usuario. Representan un valor que puede no estar disponible de inmediato pero que se resolverá en algún momento en el futuro.
Las promesas permiten (o deberían permitir) a los desarrolladores escribir código más limpio y manejable cuando se ocupan de tareas como llamadas API, interacciones de usuarios o animaciones. Al utilizar métodos como .then(), .catch() y .finally(), las Promesas permiten una forma más intuitiva de manejar escenarios de éxito y error, evitando el notorio "infierno de devolución de llamadas".
En este artículo, usaremos el nuevo método (Promise.withResolvers() de marzo de 2024) que le permite escribir código más limpio y simple al devolver un objeto que contiene tres cosas: una nueva Promesa y dos funciones, una para resolver la Promesa. y el otro para rechazarla. Como se trata de una actualización reciente, necesitará un tiempo de ejecución de Nodo reciente (v>22) para ejecutar los ejemplos de este artículo.
En los dos siguientes fragmentos de código funcionalmente equivalentes, podemos comparar el enfoque antiguo y el nuevo enfoque de asignar el método para resolver o rechazar una Promesa:
let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); Math.random() > 0.5 ? resolve("ok") : reject("not ok");
En el código anterior, puedes ver el uso más tradicional de una Promesa: creas una instancia de un nuevo objeto de promesa y luego, en el constructor, tienes que asignar las dos funciones, resolver y rechazar, que se invocarán cuando necesario.
En el siguiente fragmento de código, el mismo fragmento de código se ha reescrito con el nuevo método Promise.withResolvers() y parece más simple:
const { promise, resolve, reject } = Promise.withResolvers(); Math.random() > 0.5 ? resolve("ok") : reject("not ok");
Aquí puedes ver cómo funciona el nuevo enfoque. Devuelve la Promesa, en la que puedes invocar el método .then() y las dos funciones, resolver y rechazar.
El enfoque tradicional de Promises encapsula la lógica de creación y manejo de eventos dentro de una sola función, lo que puede ser limitante si múltiples condiciones o diferentes partes del código necesitan resolver o rechazar la promesa.
Por el contrario, Promise.withResolvers() proporciona una mayor flexibilidad al separar la creación de la Promesa de la lógica de resolución, lo que la hace adecuada para gestionar condiciones complejas o múltiples eventos. Sin embargo, para casos de uso sencillos, el método tradicional puede resultar más sencillo y familiar para quienes están acostumbrados a los patrones de promesa estándar.
Ahora podemos probar el nuevo enfoque en un ejemplo más realista. En el código siguiente, puede ver un ejemplo sencillo de una invocación de API:
function fetchData(url) { return new Promise((resolve, reject) => { fetch(url) .then(response => { // Check if the response is okay (status 200-299) if (response.ok) { return response.json(); // Parse JSON if response is okay } else { // Reject the promise if the response is not okay reject(new Error('API Invocation failed')); } }) .then(data => { // Resolve the promise with the data resolve(data); }) .catch(error => { // Catch and reject the promise if there is a network error reject(error); }); }); } // Example usage const apiURL = ''; fetchData(apiURL) .then(data => { // Handle the resolved data console.log('Data received:', data); }) .catch(error => { // Handle any errors that occurred console.error('Error occurred:', error); });
La función fetchData está diseñada para tomar una URL y devolver una Promesa que maneja una llamada API usando la API fetch. Procesa la respuesta verificando si el estado de la respuesta está dentro del rango 200-299, lo que indica éxito.
Si tiene éxito, la respuesta se analiza como JSON y la Promesa se resuelve con los datos resultantes. Si la respuesta no es exitosa, la Promesa se rechaza con un mensaje de error apropiado. Además, la función incluye manejo de errores para detectar cualquier error de red y rechazar la Promesa si se produce dicho error.
El ejemplo demuestra cómo utilizar esta función, mostrando cómo administrar los datos resueltos con un bloque .then() y manejar los errores usando un bloque .catch(), asegurando que tanto la recuperación exitosa de datos como los errores se administren adecuadamente.
En el código siguiente, reescribimos la función fetchData() usando el nuevo método Promise.withResolvers():
function fetchData(url) { const { promise, resolve, reject } = Promise.withResolvers(); fetch(url) .then(response => { // Check if the response is okay (status 200-299) if (response.ok) { return response.json(); // Parse JSON if response is okay } else { // Reject the promise if the response is not okay reject(new Error('API Invocation failed')); } }) .then(data => { // Resolve the promise with the data resolve(data); }) .catch(error => { // Catch and reject the promise if there is a network error reject(error); }); return promise; }
Como puede ver, el código anterior es más legible y el rol del objeto Promise es claro: la función fetchData devolverá una Promise que se resolverá exitosamente o fallará, invocando – en cada caso – el método adecuado . Puede encontrar el código anterior en el repositorio llamado api.invocation.{old|new}.js.
El siguiente código explora cómo implementar un método de cancelación de promesa. Como sabrás, no puedes cancelar una Promesa en JavaScript. Las promesas representan el resultado de una operación asincrónica y están diseñadas para resolverse o rechazarse una vez creadas, sin ningún mecanismo incorporado para cancelarlas.
Esta limitación surge porque las Promesas tienen un proceso de transición de estado definido; comienzan como pendientes y, una vez liquidados, no pueden cambiar de estado. Están destinados a encapsular el resultado de una operación en lugar de controlar la operación en sí, lo que significa que no pueden influir ni cancelar el proceso subyacente. Esta elección de diseño mantiene las Promesas simples y enfocadas en representar el resultado final de una operación:
const cancellablePromise = () => { const { promise, resolve, reject } = Promise.withResolvers(); promise.cancel = () => { reject("the promise got cancelled"); }; return promise; };
En el código anterior, puedes ver el objeto llamado cancelablePromise, que es una promesa con un método cancel() adicional que, como puedes ver, simplemente fuerza la invocación del método de rechazo. Esto es simplemente azúcar sintáctico y no cancela una promesa de JavaScript, aunque puede ayudar a escribir un código más claro.
Un enfoque alternativo es utilizar AbortController y AbortSignal, que pueden vincularse a la operación subyacente (por ejemplo, una solicitud HTTP) para cancelarla cuando sea necesario. En la documentación, puede ver que el enfoque AbortController y AbortSignal es una implementación más expresiva de lo que implementamos en el código anterior: una vez que se invoca AbortSignal, la promesa simplemente se rechaza.
Otro enfoque es utilizar bibliotecas de programación reactiva como RxJS, que ofrece una implementación del patrón Observable, un control más sofisticado sobre flujos de datos asíncronos, incluidas capacidades de cancelación.
Cuando se habla de casos de uso prácticos, las promesas son adecuadas para manejar operaciones asincrónicas únicas, como la obtención de datos de una API. Por el contrario, los Observables son ideales para gestionar flujos de datos, como entradas de usuarios, eventos WebSocket o respuestas HTTP, donde se pueden emitir múltiples valores a lo largo del tiempo.
Ya aclaramos que una vez iniciadas, las Promesas no se pueden cancelar, mientras que los Observables permiten la cancelación cancelando la suscripción a la transmisión. La idea general es que, con Observables, tienes una estructura explícita de la posible interacción con el objeto:
Esto se demuestra en el siguiente código:
import { Observable } from 'rxjs'; const observable = new Observable(subscriber => { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.complete(); }); const observer = observable.subscribe({ next(x) { console.log('Received value:', x); }, complete() { console.log('Observable completed'); } }); observer.unsubscribe();
Este código no se puede reescribir con Promesas porque el Observable devuelve tres valores, mientras que una Promesa solo se puede resolver una vez.
Para experimentar más con el método de cancelación de suscripción, podemos agregar otro observador que usará el método takeWhile(): permitirá que el observador espere a que los valores coincidan con una condición específica; en el código siguiente, por ejemplo, sigue recibiendo eventos del Observable mientras el valor no sea 2:
import { Observable, takeWhile } from 'rxjs'; const observable = new Observable(subscriber => { subscriber.next(1); subscriber.next(2); subscriber.next(3); subscriber.complete(); }); const observer1 = observable.subscribe({ next(x) { console.log('Received by 1 value:', x); }, complete() { console.log('Observable 1 completed'); } }); const observer2 = observable.pipe( takeWhile(value => value != "2") ).subscribe(value => console.log('Received by 2 value:', value));
En el código anterior, el observador1 es el mismo que ya hemos visto: simplemente se suscribirá y seguirá recibiendo todos los eventos del Observable. El segundo, observador2, recibirá elementos del Observable mientras se cumpla la condición. En este caso, esto significa cuando el valor es diferente de 2.
Desde la ejecución, puedes ver cómo funcionan los dos mecanismos diferentes:
$ node observable.mjs Received by 1 value: 1 Received by 1 value: 2 Received by 1 value: 3 Observable 1 completed Received by 2 value: 1 $
En este artículo, investigamos el nuevo mecanismo para asignar una Promesa en JavaScript y presentamos algunas de las formas posibles de cancelar una Promesa antes de su finalización. También comparamos Promises con objetos Observables, que no solo ofrecen las características de Promises sino que las amplían al permitir múltiples emisiones de eventos y un mecanismo adecuado para darse de baja.
Depurar código siempre es una tarea tediosa. Pero cuanto más comprenda sus errores, más fácil será corregirlos.
LogRocket le permite comprender estos errores de formas nuevas y únicas. Nuestra solución de monitoreo de frontend rastrea la interacción del usuario con sus frontends de JavaScript para brindarle la posibilidad de ver exactamente qué hizo el usuario que provocó un error.
LogRocket registra registros de la consola, tiempos de carga de páginas, seguimientos de pila, solicitudes/respuestas de red lentas con cuerpos de encabezados, metadatos del navegador y registros personalizados. ¡Comprender el impacto de tu código JavaScript nunca será tan fácil!
Pruébalo gratis.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3