JavaScript Promise

Antes de estudiar el contenido de este capítulo, necesitas comprender qué es la programación asíncrona; puedes consultar:Programación asíncrona en JavaScript

Promise es una clase proporcionada por ECMAScript 6, cuyo propósito es escribir tareas asíncronas complejas de manera más elegante.

Promise es un objeto en JavaScript utilizado para manejar operaciones asíncronas; representa la finalización (o el fallo) final de una operación asíncrona y su valor resultante.

En pocas palabras, Promise es una "promesa" que indica que en algún momento futuro se devolverá un resultado (puede ser un resultado exitoso o un motivo de fallo).

Promise tiene tres estados:

  • pending: estado inicial, ni de éxito ni de fallo
  • fulfilled: significa que la operación se completó con éxito
  • rejected: significa que la operación falló

Ejemplo

const myPromise = new Promise((resolve, reject) => {
  // Código de operación asíncrona
 
  if (/* Operación exitosa */) {
    resolve('Resultado exitoso'); // Cambia el estado de Promise a fulfilled
  } else {
    reject('Motivo del fallo'); // Cambia el estado de Promise a rejected
  }
});

Soporte del navegador

Debido a que Promise es una adición nueva de ES6, algunos navegadores antiguos no lo admiten. Los navegadores a partir de Safari 10 de Apple y Edge 14 de Windows comenzaron a admitir las características de ES6.

A continuación se muestra el soporte de Promise en los navegadores:

Chrome 58 Edge 14 Firefox 54 Safari 10 Opera 55

Cómo usar Promise

El método then()

then()El método se utiliza para especificar la función de devolución de llamada cuando el estado de Promise cambia a fulfilled o rejected.

Ejemplo

myPromise.then(
  (result) => {
    // Manejar el caso de éxito
    console.log('Éxito:', result);
  },
  (error) => {
    // Manejar el caso de fallo
    console.error('Fallo:', error);
  }
);

El método catch()

catch()El método se utiliza específicamente para manejar situaciones en las que Promise es rechazada.

Ejemplo

myPromise
  .then((result) => {
    console.log('Éxito:', result);
  })
  .catch((error) => {
    console.error('Fallo:', error);
  });

El método finally()

finally()El método se ejecutará sin importar cuál sea el estado final de Promise.

Ejemplo

myPromise
  .then((result) => {
    console.log('Éxito:', result);
  })
  .catch((error) => {
    console.error('Fallo:', error);
  })
  .finally(() => {
    console.log('Operación completada');
  });

Encadenamiento de Promise

Una característica poderosa de Promise es que se pueden encadenar múltiples operaciones asíncronas.

Ejemplo

doFirstThing()
  .then((result) => doSecondThing(result))
  .then((newResult) => doThirdThing(newResult))
  .then((finalResult) => {
    console.log('Resultado final:', finalResult);
  })
  .catch((error) => {
    console.error('Error en algún punto de la cadena:', error);
  });

Métodos estáticos de Promise

Promise.all()

Espera a que todas las Promise se completen, o a que cualquier Promise falle.

Ejemplo

Promise.all([promise1, promise2, promise3])
  .then((results) => {
    // results es un array que contiene todos los resultados de las Promise
    console.log(results);
  })
  .catch((error) => {
    // Si cualquier Promise falla, se entrará aquí
    console.error(error);
  });

Promise.race()

Devuelve el resultado de la primera Promise que se complete (ya sea éxito o fallo).

Ejemplo

Promise.race([promise1, promise2, promise3])
  .then((result) => {
    // Usar el resultado de la primera Promise que se complete
    console.log(result);
  })
  .catch((error) => {
    // Si la primera Promise que se completa es fallida
    console.error(error);
  });

Promise.resolve() y Promise.reject()

Crea rápidamente una Promise resuelta o rechazada.

Ejemplo

const resolvedPromise = Promise.resolve('Valor resuelto inmediatamente');
const rejectedPromise = Promise.reject('Motivo de rechazo inmediato');

Ejemplos de aplicación práctica

Ejemplo 1: Usar Promise para manejar solicitudes AJAX

Ejemplo

function fetchData(url) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => {
      if (xhr.status === 200) {
        resolve(xhr.response);
      } else {
        reject(new Error(xhr.statusText));
      }
    };
    xhr.onerror = () => reject(new Error('Error de red'));
    xhr.send();
  });
}

fetchData('https://api.example.com/data')
  .then((data) => {
    console.log('Datos obtenidos con éxito:', data);
  })
  .catch((error) => {
    console.error('Error al obtener los datos:', error);
  });

Ejemplo 2: Usar una cadena de Promise para manejar múltiples operaciones asíncronas

Ejemplo

function getUser(userId) {
  return fetch(`/users/${userId}`);
}

function getPosts(userId) {
  return fetch(`/users/${userId}/posts`);
}

getUser(123)
  .then((user) => {
    console.log('Obteniendo información del usuario:', user);
    return getPosts(user.id);
  })
  .then((posts) => {
    console.log('Obteniendo publicaciones del usuario:', posts);
  })
  .catch((error) => {
    console.error('Error en la operación:', error);
  });

Preguntas frecuentes y mejores prácticas

1. Evita el anidamiento de Promise

Mala práctica (versión de Promise del infierno de callbacks):

Ejemplo

doFirstThing().then((firstResult) => {
  doSecondThing(firstResult).then((secondResult) => {
    doThirdThing(secondResult).then((thirdResult) => {
      console.log(thirdResult);
    });
  });
});

Buena práctica (usando encadenamiento):

Ejemplo

doFirstThing()
  .then(doSecondThing)
  .then(doThirdThing)
  .then((finalResult) => {
    console.log(finalResult);
  });

2. Siempre maneja los rechazos

Olvidar manejar el rechazo de Promise provocará un error de 'rechazo de Promise no capturado'. Siempre usa.catch()otry/catch(en async/await) para manejar errores.

3. Promise no es cancelable

Una vez creada, una Promise no se puede cancelar. Si necesitas funcionalidad de cancelación, puedes considerar usar AbortController u otros patrones.

4. Respuestas a preguntas frecuentes (FAQ)

P: ¿Se puede invertir el orden de la secuencia then, catch y finally?

R: Sí, el efecto es exactamente el mismo. Pero no se recomienda hacerlo; es mejor escribir el programa en el orden then-catch-finally.

P: Aparte del bloque then, ¿se pueden usar los otros dos bloques varias veces?

R: Sí, finally se ejecutará en orden al igual que then, pero solo se ejecutará el primer bloque catch, a menos que haya una excepción dentro del bloque catch. Por lo tanto, es mejor tener solo un bloque catch y un finally.

P: ¿Cómo se interrumpe un bloque then?

R: El bloque then se ejecuta secuencialmente hacia abajo por defecto; return no puede interrumpirlo. Puedes usar throw para saltar a catch y así interrumpirlo.

P: ¿Cuándo es adecuado usar Promise en lugar de las funciones de devolución de llamada tradicionales?

R: Cuando necesitas ejecutar operaciones asíncronas secuencialmente varias veces. Por ejemplo, si quieres verificar primero el nombre de usuario y luego la contraseña mediante métodos asíncronos, es decir, necesitas verificar el nombre de usuario de forma asíncrona y luego la contraseña, Promise es muy adecuada.

P: ¿Promise es un método que convierte lo asíncrono en síncrono?

R: En absoluto. Promise es simplemente un estilo de programación mejor.

P: ¿Cuándo necesitamos escribir otro then en lugar de continuar programando en el then actual?

R: Cuando necesitas volver a llamar a una tarea asíncrona.


Promise y async/await

ES2017 introdujo async/await, que se basa en Promise y proporciona una sintaxis más intuitiva para manejar operaciones asíncronas.

Ejemplo

async function fetchData() {
  try {
    const user = await getUser(123);
    const posts = await getPosts(user.id);
    console.log('Publicaciones del usuario:', posts);
  } catch (error) {
    console.error('Error al obtener los datos:', error);
  }
}

Aunque async/await es más legible, sigue siendo importante comprender los conceptos básicos de Promise, porque es la base de async/await.

Otras extensiones