"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Dominar la cancelación de promesas en JavaScript

Dominar la cancelación de promesas en JavaScript

Publicado el 2024-11-02
Navegar:688

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.

Comparación de los métodos de promesa de JavaScript antiguos y nuevos

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.

Ejemplo del mundo real: llamar a una API

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.

Cancelación de promesas

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.

Una comparación entre observables y promesas

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:

  • Creas un Observable y luego todos los Observables pueden suscribirse a él
  • El Observable realiza su trabajo, cambiando de estado y emitiendo eventos. Todos los observadores recibirán las actualizaciones; esta es la principal diferencia con Promises. Una Promesa se puede resolver solo una vez mientras que los Observables pueden seguir emitiendo eventos mientras haya Observadores
  • Una vez que el Observador no está interesado en los eventos de los Observables, puede darse de baja, liberando recursos

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
$

Conclusión

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.


LogRocket: depura errores de JavaScript más fácilmente al comprender el contexto

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.

Mastering promise cancellation in JavaScript

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.

Declaración de liberación Este artículo se reproduce en: https://dev.to/logrocket/mastering-promise-cancellation-in-javascript-1eb7?1 Si hay alguna infracción, comuníquese con [email protected] para eliminarla.
Último tutorial Más>

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