HOWTO · Angular
Angular Observable VS. Promise
En este artículo, explicaremos qué es Observable y Promise en Angular. Además, explicaremos la diferencia fundamental entre observable y Promise.
En esta página
Elige una Promise para obtener un único resultado eventual y un Observable cuando la tarea se represente de forma natural como valores a lo largo del tiempo. En Angular, estas abstracciones no son intercambiables: la elección depende del comportamiento que necesite tu código.
Qué es Promise en Angular
Una Promise es un objeto de JavaScript que representa un resultado eventual: puede cumplirse con un valor o rechazarse con un motivo. Pasa de pendiente a cumplida o rechazada, y sus métodos then, catch y finally permiten encadenar operaciones y gestionar sus resultados. Una Promise se resuelve una sola vez; una Promise cumplida no emite después otro valor.
El ejecutor de una Promise comienza a ejecutarse cuando se construye la Promise. La API de Promise no incluye una operación integrada de cancelación o reintento, pero el código de la aplicación puede implementar trabajo que admita cancelación o lógica de reintento alrededor de una Promise. Además, las cadenas largas pueden ser más difíciles de seguir a medida que crece el flujo de trabajo.
Qué es Observable en Angular
Un Observable representa un flujo que puede emitir cero o más valores y luego completarse o producir un error; también puede permanecer abierto indefinidamente sin ninguna de esas notificaciones terminales. En Angular, HttpClient devuelve Observables de RxJS. El trabajo de un Observable suele comenzar al suscribirse, aunque depende de cómo se haya creado. Los operadores de RxJS pueden transformar y combinar flujos, y cancelar la suscripción detiene la entrega de valores y puede cancelar el trabajo subyacente si el productor admite esa cancelación.
Un Observable no proporciona automáticamente cancelación, reintentos ni un mejor rendimiento en todos los casos: esos comportamientos dependen del productor y de sus operadores. RxJS ofrece herramientas útiles, aunque aprender a usarlas tiene un coste para quienes aún no conocen los flujos.
Diferencias clave entre Promise y Observable en Angular
| Aspecto | Promise | Observable |
|---|---|---|
| Valores | Un valor de cumplimiento o un motivo de rechazo | Cero o más valores next; el flujo puede completarse, producir un error o permanecer abierto sin ninguna de esas notificaciones |
| Inicio | El ejecutor se ejecuta al construirla | A menudo comienza al suscribirse; el momento depende de cómo se cree |
| Errores | Se gestionan con catch o un controlador de rechazo |
Se gestionan en la suscripción o con operadores como catchError |
| Cancelación | No tiene un método de cancelación integrado | Cancelar la suscripción detiene la observación y puede cancelar el trabajo si este lo admite |
| Cuándo conviene | Para un único resultado, como el de un cálculo sencillo | Para eventos, valores cambiantes o trabajo que se beneficie de la composición de RxJS |
| Reintento | Sin reintento integrado; vuelve a llamar tú mismo a la función productora | Operadores como retry vuelven a suscribirse a la fuente tras un fallo |
| Uso compartido | Todos los controladores observan el mismo resultado único ya resuelto | Un Observable frío ejecuta su productor de nuevo para cada suscriptor; comparte explícitamente una fuente caliente cuando una sola ejecución debe atender a muchos suscriptores |
Para una solicitud HTTP en Angular, usa el Observable de HttpClient cuando necesites composición con RxJS o un flujo de cancelación. La respuesta puede contener un array, pero eso no exige usar un Observable por tener varios elementos: la solicitud sigue produciendo un único valor de respuesta. Una Promise también puede representar esa respuesta. Elige la abstracción que mejor encaje con la tarea y las API que la rodean.
Cancelación, ejecución diferida y reintentos en detalle
La cancelación muestra la diferencia práctica más clara. En un campo de búsqueda que busca mientras se escribe, cada nueva pulsación debería abandonar la solicitud del término anterior. Con un Observable de HttpClient, cambiar a una nueva solicitud con switchMap cancela la suscripción anterior y HttpClient cancela entonces esa solicitud en curso. Una Promise no ofrece un cambio equivalente: una vez iniciado, el trabajo subyacente sigue ejecutándose, salvo que la API llamada proporcione su propio mecanismo de cancelación, como un AbortController para fetch, que es independiente de la propia Promise.
El momento de ejecución también difiere. Una Promise inicia su trabajo al construirse, adjunte o no algún código un controlador, por lo que crear la Promise ya es un efecto secundario. Un Observable frío, en cambio, es diferido: crearlo solo describe el trabajo, cada llamada a subscribe ejecuta de nuevo el productor y, por tanto, dos suscriptores pueden desencadenar dos ejecuciones separadas. El código que espera una única ejecución compartida debe indicarlo explícitamente, por ejemplo con un operador de uso compartido o almacenando una Promise.
Los reintentos y la composición siguen el mismo patrón. Una Promise rechazada está terminada; recuperarse significa volver a llamar a la función productora en tu propio bucle. Un Observable puede, en cambio, volver a suscribirse a su fuente con el operador retry, y los patrones temporales que alrededor de una Promise necesitarían temporizadores y banderas escritos a mano, como esperar una pausa en las pulsaciones con debounceTime o ignorar entradas sin cambios con distinctUntilChanged, son operadores únicos en RxJS.
Cómo elegir en la práctica
Recurre a una Promise cuando el resultado sea realmente único y desechable: un cálculo puntual, un SDK de terceros que ya devuelve una Promise o un flujo lineal corto que se lee mejor con async y await y nunca necesita cancelarse. Recurre a un Observable cuando la fuente sea un flujo por naturaleza, como respuestas de HttpClient que quieres componer o cancelar, parámetros de ruta, cambios de valores de un formulario, eventos del DOM o un WebSocket, y siempre que importen los suscriptores tardíos, los valores repetidos o la limpieza. Ante la duda, conserva el tipo que ya devuelve la API productora en lugar de convertir en cada límite.
Convertir entre Promise y Observable
Los dos modelos interoperan, pero la conversión no es gratuita. En RxJS, from envuelve una Promise como un Observable que emite el valor resuelto una vez, mientras que firstValueFrom y lastValueFrom convierten un Observable de nuevo en una Promise para código con async y await. Convertir así un Observable de HttpClient conserva la única respuesta, pero renuncia a la cancelación que ofrecía cancelar la suscripción y pone fin a cualquier otra composición de operadores en ese punto. Convierte en un límite claro, como el borde de un servicio, en lugar de hacerlo dentro de lógica de flujos reutilizable.
Errores frecuentes
- Tratar un array dentro de una única respuesta HTTP como emisiones múltiples. La solicitud sigue emitiendo una sola vez; solo una fuente que realmente se repite emite repetidamente.
- Convertir una solicitud en una Promise y esperar después que salir de la página la cancele. Sin una suscripción que cancelar, nada indica al productor que se detenga.
- Suscribirse en un componente de larga duración sin una estrategia de limpieza, como el pipe
asyncotakeUntilDestroyed, de modo que las suscripciones obsoletas siguen recibiendo valores después de que la vista desaparece. - Esperar que una Promise ya resuelta recoja después un valor más reciente. Una Promise memoriza su único resultado; leer el estado actual requiere una nueva llamada o un flujo.
Ejemplo de Promise y Observable en Angular
Este ejemplo independiente de JavaScript usa un pequeño Observable didáctico para mostrar la suscripción y las emisiones múltiples. No es la implementación de RxJS que utiliza Angular. El Observable envía queued y saved durante la suscripción; el callback de Promise se ejecuta después, en una microtarea.
Ejecuta este ejemplo independiente:
class Observable {
constructor(producer) {
this.producer = producer;
}
subscribe(next) {
const teardown = this.producer(next);
return { unsubscribe: teardown };
}
}
const promise = Promise.resolve("saved");
const updates = new Observable((next) => {
next("queued");
next("saved");
return () => {};
});
console.log("before subscription");
updates.subscribe((value) => console.log(`Observable: ${value}`));
promise.then((value) => console.log(`Promise: ${value}`));
console.log("after scheduling");
before subscription
Observable: queued
Observable: saved
after scheduling
Promise: saved
El Observable didáctico llama a su productor al suscribirse y envía dos valores; la Promise se resuelve una sola vez y su callback se ejecuta en una microtarea posterior. Las etiquetas visibles del ejemplo sin modificar permanecen en inglés y muestran esos dos valores antes del callback. En el código de una aplicación, usa Observable de RxJS y sus API de suscripción. Elige Promise para un único resultado y Observable cuando la composición de flujos, las emisiones repetidas o la gestión de suscripciones se ajusten a la tarea.
Ejemplo: Promise inmediata, Observable diferido y cancelable
Este segundo ejemplo independiente usa el mismo Observable didáctico para mostrar las filas de momento de ejecución y cancelación de la tabla. El ejecutor de Promise se ejecuta antes de que alguien adjunte un controlador, mientras que el productor del Observable espera a subscribe y ejecuta su limpieza cuando el suscriptor cancela la suscripción tras la segunda marca. El callback de Promise se ejecuta de nuevo como una microtarea, antes de la primera marca del temporizador. Las etiquetas del ejemplo sin modificar permanecen en inglés.
class Observable {
constructor(producer) {
this.producer = producer;
}
subscribe(next) {
const teardown = this.producer(next);
return { unsubscribe: teardown };
}
}
const promise = new Promise((resolve) => {
console.log("Promise executor: runs at construction");
resolve("done");
});
const ticks = new Observable((next) => {
console.log("Observable producer: runs at subscription");
let count = 0;
const id = setInterval(() => next(++count), 10);
return () => {
clearInterval(id);
console.log("teardown: interval cleared");
};
});
console.log("before subscription");
const sub = ticks.subscribe((value) => {
console.log(`tick ${value}`);
if (value === 2) sub.unsubscribe();
});
promise.then((value) => console.log(`Promise: ${value}`));
Promise executor: runs at construction
before subscription
Observable producer: runs at subscription
Promise: done
tick 1
tick 2
teardown: interval cleared
La ejecución confirma los tres comportamientos en orden: el ejecutor se disparó al construirse, el productor permaneció inactivo hasta la suscripción y cancelar la suscripción ejecutó la limpieza que eliminó el intervalo, por lo que no llegan más marcas.