Qué es un observable
Un observable es un stream de cero o más valores emitidos en el tiempo: el cuarto cuadrante que unifica eventos, temporizadores, red y cualquier fuente asíncrona bajo una misma álgebra perezosa y componible.
Un valor es una foto; un observable es una película. Donde una variable guarda un dato y una Promise promete uno solo, un observable modela algo más general y más honesto con la realidad del navegador: una secuencia de cero, uno o infinitos valores que llegan repartidos en el tiempo. Clics, mensajes de un websocket, latidos de un temporizador, respuestas de red: todo cabe en la misma abstracción. Entender el observable es adquirir el lenguaje universal de lo asíncrono y lo reactivo.
- Situar el observable en el cuadrante único/múltiple y síncrono/asíncrono.
- Leer un stream como una línea temporal de emisiones.
- Reconocer qué fuentes del mundo real son, en el fondo, observables.
- Distinguir observable de
Promise,Iterabley valor plano.
El cuadrante que lo ordena todo
La forma más nítida de definir un observable es por oposición. Cruza dos ejes: cuántos valores produce la fuente (uno o muchos) y cuándo los entrega (ahora, de forma síncrona, o luego, de forma asíncrona). Salen cuatro celdas, y cada una tiene su primitivo canónico en JavaScript.
| Un valor | Múltiples valores | |
|---|---|---|
| Síncrono | valor plano · leer() |
Iterable · array, generador |
| Asíncrono | Promise |
Observable |
Una función que retorna T te da un dato ya. Un Iterable te da muchos, pero tú los pides tirando (pull) uno a uno con next. Una Promise te dará un valor futuro, pero solo uno, sin cancelación real y ejecutándose antes de que la escuches. El observable ocupa la esquina que faltaba: muchos valores, en el futuro, empujados (push) hacia ti. Esa combinación es exactamente la forma de casi todo lo interesante en una interfaz.
const valor: number = leer(); // 1 valor, ya, sincrono
const lista: Iterable<number> = [1, 2, 3]; // N valores, pull, sincrono
const futuro: Promise<number> = pedirNum(); // 1 valor, async, eager
const flujo$: Observable<number> = clics$; // N valores, async, push
Durante años ese cuarto cuadrante no tuvo un primitivo nativo: la web lo suplía con callbacks sueltos, y cada API inventaba el suyo. El observable llegó para nombrarlo con precisión, y esa es la razón de su enorme influencia conceptual.
La diferencia profunda no es la cantidad, es quién decide el ritmo. En un Iterable el consumidor decide cuándo pedir el siguiente valor: es pull, la fuente es pasiva. En un observable la fuente decide cuándo emitir y te lo empuja: es push, el consumidor es reactivo. Por eso el observable modela eventos que no controlas —un clic, un paquete de red, un tick de reloj— mientras el iterable modela colecciones que tú recorres a tu antojo.
El stream como línea temporal
Piensa en un observable como una cinta que avanza de izquierda a derecha. Sobre ella caen valores en instantes concretos, y la cinta termina de una de dos maneras: con un cierre limpio o con un error. Esta es la notación de canicas (marble diagrams), el lenguaje visual con el que la comunidad razona sobre streams desde hace más de una década.
flowchart LR T0[inicio] --> A[valor 42] A --> B[valor 7] B --> C[valor 13] C --> D[complete] style D fill:#89b4fa,color:#11111b
Ese diagrama describe un stream finito: emite 42, 7, 13 y se completa. Pero la abstracción es más amplia, y sus casos extremos son igual de válidos. Un stream puede no emitir nunca y solo completar; puede emitir para siempre y no completar jamás; puede fallar sin haber emitido nada. Cada forma tiene su constructor:
import { of, EMPTY, NEVER, throwError } from 'rxjs';
of(42, 7, 13); // emite tres valores y completa
EMPTY; // completa sin emitir nada (vacio)
NEVER; // no emite ni completa jamas
throwError(() => new Error('roto')); // termina con error, sin next
La firma mental que gobierna todas es una gramática estricta: cero o más next, seguidos como mucho de un complete o un error, nunca ambos, nunca nada después del cierre. Ese contrato es lo que separa un observable de un simple callback: el callback no tiene noción de “terminé” ni de “fallé”, y por eso no compone. La lección siguiente diseca esa gramática pieza a pieza.
Todo es un stream
El giro conceptual que desbloquea la programación reactiva es dejar de ver estas fuentes como cosas distintas y verlas como el mismo tipo. La API de 2026 es idéntica sin importar el origen:
import { fromEvent, interval, from } from 'rxjs';
// Eventos del DOM: un stream de clics que no termina nunca
const clics$ = fromEvent<PointerEvent>(boton, 'click');
// Tiempo: un stream que emite 0, 1, 2... cada segundo
const tic$ = interval(1000);
// Una Promise adaptada: un stream de exactamente un valor y complete
const perfil$ = from(fetch('/api/perfil').then((r) => r.json()));
Fíjate en la convención $ al final del nombre: es la notación finlandesa que marca “esto es un stream, no un valor”. Los tres son Observable, aunque su comportamiento temporal sea opuesto. Y aquí está la ganancia estructural: al compartir tipo, comparten operadores. El mismo map, el mismo filter, el mismo debounceTime que aplicas a los clics sirve para el temporizador y para la red. Unificar la representación multiplica la reutilización y elimina las cuatro APIs distintas que la plataforma te obligaría a memorizar.
El mismo map cae sobre las tres fuentes sin inmutarse por su naturaleza temporal:
import { map } from 'rxjs';
const posX$ = clics$.pipe(map((e) => e.clientX)); // stream infinito de eventos
const ticDoble$ = tic$.pipe(map((n) => n * 2)); // temporizador periodico
const nombre$ = perfil$.pipe(map((p) => p.nombre)); // una sola respuesta de red
Esa indiferencia a la fuente es la esencia del modelo: escribes la lógica una vez y sirve para eventos, tiempo y red por igual. Ningún otro primitivo de JavaScript te da esa uniformidad.
Observable no es Promise
El error más común de quien llega desde async/await es tratar el observable como una Promise con más valores. Comparten territorio, pero difieren en tres ejes decisivos:
Multiplicidad
Una Promise se resuelve una vez y queda fija. Un observable emite cero, uno o infinitos valores; el “una sola vez” es solo su caso degenerado.
Pereza
Una Promise empieza a ejecutarse en el momento de crearse, la escuches o no. Un observable es perezoso: no hace nada hasta que alguien se suscribe.
Cancelación
Cancelar una Promise es un ejercicio de disciplina con AbortController. Cancelar un observable es su naturaleza: te desuscribes y el trabajo se detiene.
Composición
Encadenas Promise con then. Compones observables con un álgebra de operadores que transforman, filtran y combinan streams enteros.
La pereza y la cancelación se ven mejor en código que en palabras. Crear el observable no imprime nada; solo la suscripción arranca al productor, y solo la desuscripción lo detiene:
const espia$ = new Observable<number>((sub) => {
console.log('productor arranca'); // solo al suscribir
const id = setInterval(() => sub.next(Date.now()), 500);
return () => clearInterval(id); // teardown: al desuscribir
});
const s = espia$.subscribe((v) => console.log(v));
setTimeout(() => s.unsubscribe(), 2000); // corta el trabajo limpiamente
Una Promise no ofrece ninguna de estas dos propiedades: es ansiosa e incancelable por diseño. Por eso, aunque async/await sea perfecto para una petición puntual, no puede modelar una fuente que emite en el tiempo y que a veces hay que abortar.
Que todo pueda modelarse como stream no significa que todo deba serlo. Un dato que se lee una vez y no cambia es una constante; un valor que siempre tiene un presente es un signal. Reservar el observable para lo que de verdad fluye en el tiempo mantiene el código honesto y legible. La última lección de este nivel afina justo esa frontera entre stream y estado.
Un valor responde a la pregunta qué. Un observable responde a la pregunta qué y cuándo a la vez, y lo hace sin obligarte a distinguir la naturaleza de la fuente. Ahí reside su poder unificador: colapsa cuatro problemas que la plataforma resuelve con cuatro APIs distintas —eventos con addEventListener, tiempo con setInterval, red con fetch, cancelación con AbortController— en una única álgebra de streams. Cuando modelas una interfaz como una composición de observables dejas de escribir callbacks sueltos que mutan variables compartidas, la fuente número uno de bugs de estado, y empiezas a declarar cómo fluye la información desde sus orígenes hasta la pantalla. El estado deja de ser algo que empujas a mano y pasa a ser algo derivado: una función pura de los streams de entrada. Esa inversión —de estado imperativo mutado a estado declarativo derivado— es la misma idea que verás luego en signals, en Redux y en las arquitecturas unidireccionales. El observable es, históricamente, quien la trajo a la web, y por eso dominarlo te da el modelo mental que subyace a casi todo lo demás del track.
- Enumera cinco fuentes de datos de una app que uses y ubica cada una en su cuadrante del cruce único/múltiple contra síncrono/asíncrono.
- Dibuja el diagrama de canicas de un buscador: pulsaciones de tecla que emiten para siempre y no completan.
- Explica con precisión por qué una
Promiseno puede representar el movimiento del ratón y un observable sí. - Toma un
addEventListenerque hayas escrito y descríbelo con el vocabulario denext,errorycomplete, incluyendo cuándo debería “completar”.