Diferencia entre forkJoin y combineLatest
Cuando se combinan múltiples Observables en RxJS, forkJoin y combineLatest son las Creation Functions más utilizadas. Sin embargo, estas dos se comportan de manera muy diferente y si no se utilizan correctamente, no producirán los resultados esperados.
Esta página compara a fondo las diferencias entre ellas con ilustraciones y ejemplos prácticos para que quede claro cuál debes usar.
Conclusión: la diferencia entre forkJoin y combineLatest
| Característica | forkJoin | combineLatest |
|---|---|---|
| Tiempo de emisión | Solo una vez tras completarse todos | Cada vez que se actualice un valor |
| Valor emitido | El último valor de cada Observable | El valor más reciente de cada Observable |
| Condición de finalización | Todos los Observables completados | Todos los Observables completados |
| Uso principal | Adquisición paralela de APIs, carga inicial | Monitorización de formularios, sincronización en tiempo real |
| Stream infinito | ❌ No utilizable (no se completa) | ✅ Utilizable (emite valores sin completarse) |
TIP
Fácil de recordar
forkJoin= «salir una sola vez cuando todos están presentes» (similar a Promise.all)combineLatest= «reportar el último estado cada vez que alguien se mueva»
Entender las diferencias de comportamiento con las ilustraciones
Comportamiento de forkJoin
Punto: esperar hasta que todos los Observables estén complete y emitir solo el último valor una vez.
Comportamiento de combineLatest
Punto: después de que todos los Observables hayan emitido su primer valor, seguirán emitiendo la última combinación cada vez que se actualice cualquiera de ellos.
Diferencias en la línea de tiempo
Comparación práctica: verificar el comportamiento con la misma fuente de datos
Apliquemos forkJoin y combineLatest al mismo Observable y verifiquemos las diferencias en la salida.
import { forkJoin, combineLatest, interval, take, map } from 'rxjs';
// crear área de salida
const output = document.createElement('div');
output.innerHTML = '<h3>Comparación forkJoin vs combineLatest:</h3>';
document.body.appendChild(output);
// Crear 2 Observables
const obs1$ = interval(1000).pipe(
take(3),
map(i => `A${i}`)
);
const obs2$ = interval(1500).pipe(
take(2),
map(i => `B${i}`)
);
// Área de visualización del resultado de forkJoin
const forkJoinResult = document.createElement('div');
forkJoinResult.innerHTML = '<h4>forkJoin:</h4><div id="forkjoin-output">esperando...</div>';
output.appendChild(forkJoinResult);
// Área de visualización del resultado de combineLatest
const combineLatestResult = document.createElement('div');
combineLatestResult.innerHTML = '<h4>combineLatest:</h4><div id="combinelatest-output"></div>';
output.appendChild(combineLatestResult);
// forkJoin:tras completarse todos1emite una sola vez
forkJoin([obs1$, obs2$]).subscribe(result => {
const el = document.getElementById('forkjoin-output');
if (el) {
el.textContent = `emisión: [${result.join(', ')}]`;
el.style.color = 'green';
el.style.fontWeight = 'bold';
}
});
// combineLatest:emite cada vez que se actualiza un valor
const combineOutput = document.getElementById('combinelatest-output');
combineLatest([obs1$, obs2$]).subscribe(result => {
if (combineOutput) {
const item = document.createElement('div');
item.textContent = `emisión: [${result.join(', ')}]`;
combineOutput.appendChild(item);
}
});Resultado de la ejecución:
forkJoin: emite[A2, B1]solo una vez después de aproximadamente 3 segundoscombineLatest: emite 4 veces a partir de aprox. 1,5 s (ej.[A0, B0]→[A1, B0]→[A2, B0]→[A2, B1])
NOTE
El orden de emisión de
combineLatestdepende de la programación del temporizador y puede variar según el entorno. Lo importante es que «un valor se emite cada vez que uno de ellos se actualiza». En el ejemplo anterior la emisión se produce 4 veces, pero el orden puede cambiar, por ejemplo[A1, B0]→[A1, B1].
Cuál usar (guía por caso)
Casos en los que usar forkJoin
1. Adquisición paralela de múltiples APIs
Cuando quieres procesar los datos después de que todos los datos estén disponibles.
import { forkJoin } from 'rxjs';
import { ajax } from 'rxjs/ajax';
// obtener info de usuario y configuración simultáneamente
forkJoin({
user: ajax.getJSON('/api/user/123'),
settings: ajax.getJSON('/api/settings'),
notifications: ajax.getJSON('/api/notifications')
}).subscribe(({ user, settings, notifications }) => {
// renderizar la pantalla tras tener todos los datos
renderDashboard(user, settings, notifications);
});2. Adquisición de datos por lotes durante la carga inicial
Adquirir en bloque los datos maestros necesarios al iniciar la aplicación.
import { forkJoin } from 'rxjs';
import { ajax } from 'rxjs/ajax';
function loadInitialData() {
return forkJoin({
categories: ajax.getJSON('/api/categories'),
countries: ajax.getJSON('/api/countries'),
currencies: ajax.getJSON('/api/currencies')
});
}WARNING
forkJoinno puede usarse con Observables que no se completan (por ejemplo,interval, WebSocket, flujos de eventos). Si no se completa, seguirá esperando indefinidamente.
Casos en los que usar combineLatest
1. Monitorización en tiempo real de la entrada del formulario
Combinar múltiples valores de entrada para actualizar la validación y la visualización.
import { combineLatest, fromEvent } from 'rxjs';
import { map, startWith } from 'rxjs';
const name$ = fromEvent(nameInput, 'input').pipe(
map(e => (e.target as HTMLInputElement).value),
startWith('')
);
const email$ = fromEvent(emailInput, 'input').pipe(
map(e => (e.target as HTMLInputElement).value),
startWith('')
);
const age$ = fromEvent(ageInput, 'input').pipe(
map(e => parseInt((e.target as HTMLInputElement).value) || 0),
startWith(0)
);
// ejecutar validación cada vez que cambia una entrada
combineLatest([name$, email$, age$]).subscribe(([name, email, age]) => {
const isValid = name.length > 0 && email.includes('@') && age >= 18;
submitButton.disabled = !isValid;
});2. Sincronización en tiempo real de múltiples streams
Visualización integrada de datos y estado de sensores.
import { combineLatest, interval } from 'rxjs';
import { map } from 'rxjs';
const temperature$ = interval(2000).pipe(map(() => 20 + Math.random() * 10));
const humidity$ = interval(3000).pipe(map(() => 40 + Math.random() * 30));
const pressure$ = interval(2500).pipe(map(() => 1000 + Math.random() * 50));
combineLatest([temperature$, humidity$, pressure$]).subscribe(
([temp, humidity, pressure]) => {
updateDashboard({ temp, humidity, pressure });
}
);3. Combinación de condiciones de filtrado
Realizar una búsqueda cada vez que cambien varias condiciones de filtro.
import { combineLatest, BehaviorSubject } from 'rxjs';
import { debounceTime, switchMap } from 'rxjs';
const searchText$ = new BehaviorSubject('');
const category$ = new BehaviorSubject('all');
const sortOrder$ = new BehaviorSubject('asc');
combineLatest([searchText$, category$, sortOrder$]).pipe(
debounceTime(300),
switchMap(([text, category, sort]) =>
fetchProducts({ text, category, sort })
)
).subscribe(products => {
renderProductList(products);
});Diagrama de flujo de uso
Errores comunes y soluciones
Error 1: usar forkJoin con un Observable que no se completa
// ❌ esto nunca se emitirá
forkJoin([
interval(1000), // no se completa
ajax.getJSON('/api/data')
]).subscribe(console.log);
// ✅ takecompletar con combineLatestusar
forkJoin([
interval(1000).pipe(take(5)), // 5se completa en
ajax.getJSON('/api/data')
]).subscribe(console.log);Error 2: sin valor inicial en combineLatest
// ❌ name$hasta la primera emisión de Bemail$aunque haya valores, nada se emite
combineLatest([name$, email$]).subscribe(console.log);
// ✅ startWithestablecer valor inicial con
combineLatest([
name$.pipe(startWith('')),
email$.pipe(startWith(''))
]).subscribe(console.log);Resumen
| Criterio de selección | forkJoin | combineLatest |
|---|---|---|
| Procesar una sola vez cuando todos estén listos | ✅ | ❌ |
| Procesar cada vez que cambia un valor | ❌ | ✅ |
| Stream que no se completa | ❌ | ✅ |
| Uso similar a Promise.all | ✅ | ❌ |
| Sincronización en tiempo real | ❌ | ✅ |
IMPORTANT
Principio de uso
- forkJoin: «una sola vez cuando todos están presentes» → adquisición paralela de APIs, carga inicial
- combineLatest: «actualizar cada vez que alguien se mueva» → monitorización de formularios, UI en tiempo real
Páginas relacionadas
- forkJoin - Explicación detallada de forkJoin
- combineLatest - Explicación detallada de combineLatest
- zip - Emparejar valores correspondientes
- merge - Ejecutar múltiples Observables en paralelo
- withLatestFrom - Solo el flujo principal es trigger