Let's look at this piece of code:
let isNull = true;
const a$ = new Subject<void>();
const b$ = a$.pipe(
switchMap(() => {
if (!isNull)
return of(true).pipe(
tap(() => console.log('should be shared')),
delay(100) // (3)
);
else return of(null);
}),
shareReplay(1)
);
b$.subscribe((b) => console.log('1', b));
a$.next(); // (1)
b$.subscribe((b) => console.log('2', b));
isNull = false;
a$.next(); // (2)
b$.subscribe((b) => console.log('3', b));
The output is the following:
1 null
2 null
should be shared
3 null // *
1 true
2 true
3 true
Expected output:
1 null
2 null
should be shared
1 true
2 true
3 true
The line marked with *
is not desired and I am unsure where it comes from. I assume that the last Observable switchMap
returns is not the one updated by (2) but the old one from (1). If I remove the delay
at (3), the line marked with *
does not appear as expected. This sounds like a timing/ scheduling issue but I do not know a way
next
ingEDIT: Made the example more concise.
EDIT 2:
The reason why I am getting null
is that during the time of the third subscription, shareReplay
does not have the fresh value (true
) yet. Instead, it returns the stale value, which is null
. A partially correct workaround is to do this:
let isNull = true;
const a$ = new Subject<void>();
const b$ = a$.pipe(
switchMap(() => {
if (!isNull)
return of(true).pipe(
tap(() => console.log('should be shared')),
delay(100),
shareReplay(1)
);
else return of(null).pipe(shareReplay(1));
}),
share()
);
b$.subscribe((b) => console.log('1', b));
a$.next();
b$.subscribe((b) => console.log('2', b));
isNull = false;
a$.next();
b$.subscribe((b) => console.log('3', b));
// Output:
/*
1 null
should be shared
1 true
2 true
3 true
*/
But as you see, the output "2 null" is missing. So, I am still not sure how to solve this issue elegantly.
The reason why null
is logged instead of true
is that the emission of true
reaches the shareReplay(1)
operator after it has already returned the previously buffered value (null
) to the second subscriber.
This happens because the delay
operator schedules the emission of true
within a macrotask, which occurs after the synchronous subscription process is completed (wherein shareReplay(1)
returns its buffered value to the subscriber). Therefore, the update of shareReplay(1)
's buffer does not happen in time or, to put it another way, the subscriber does not wait for the update to complete.
This should solve the problem (adapted from here):
let isNull = true;
const a$ = new Subject<void>();
const b$ = a$.pipe(
map(() => {
if (!isNull)
return of(true).pipe(
tap(() => console.log('should be shared')),
delay(100),
shareReplay(1)
);
else return of(null);
}),
shareReplay(1),
switchAll()
);
b$.subscribe((b) => console.log('1', b));
a$.next();
b$.subscribe((b) => console.log('2', b));
isNull = false;
a$.next();
b$.subscribe((b) => console.log('3', b));
// Output:
/*
1 null
2 null
should be shared
1 true
2 true
3 true
*/