コンテンツにスキップ

mergeMap (flatMap)

ソースの各値を Observable に射影し、すべての内部 Observable をマージします。 switchMap とは異なり、以前の内部 Observable をキャンセルしません。

Timeline of observable eventsABC
mergeMap(fn)
Timeline of observable eventsA1A2A3B1B2C1B3C2C3
// Timeline:
//
// input$ A B C
// inner A A1 A2 A3
// inner B B1 B2 B3
// inner C C1 C2 C3
// result$ A1 A2 A3 B1 B2 C1 B3 C2 C3
//
// Explanation:
// - mergeMap creates an inner observable for each source value
// - Unlike switchMap, previous inner observables are NOT cancelled
// - B's inner continues even after C arrives (B3 is still emitted)
// - All inner observables run concurrently and their results are merged
const input$ = source<string>();
const result$ = input$.pipe(
mergeMap((letter) => {
const inner$ = source<string>();
setTimeout(() => {
inner$.next(`${letter}1`);
}, 10);
setTimeout(() => {
inner$.next(`${letter}2`);
}, 110);
setTimeout(() => {
inner$.next(`${letter}3`);
}, 210);
return inner$;
}),
);
const valueHistory: string[] = [];
result$.subscribe((value) => {
valueHistory.push(value);
});
const sleep = (ms: number): Promise<void> =>
new Promise((resolve) => {
setTimeout(resolve, ms);
});
// Emit A - inner emits A1, A2, A3 at 10ms, 110ms, 210ms
input$.next('A');
await sleep(250);
assert.deepStrictEqual(valueHistory, ['A1', 'A2', 'A3']);
// Emit B - inner starts emitting B1, B2 at 10ms, 110ms
input$.next('B');
await sleep(150);
assert.deepStrictEqual(valueHistory, ['A1', 'A2', 'A3', 'B1', 'B2']);
// Emit C while B's inner is still running (B3 at 210ms not yet fired)
// Unlike switchMap, B's inner is NOT cancelled
input$.next('C');
await sleep(250);
// B3 appears between C1 and C2, showing the merge behavior
assert.deepStrictEqual(valueHistory, [
'A1',
'A2',
'A3',
'B1',
'B2',
'C1',
'B3',
'C2',
'C3',
]);

View source on GitHub