I am applying exhaustMap to an observable from which two projected observables get created. The 2nd of the two projected observables should not begin emitting until after the first projected observable completes. So I would expect the output observable to emit the items from both projected observables. But for some reason, the 2nd observable is being ignored. Notice that the 2nd projected observable does not begin emitting until 7 seconds have passed -- which is well past the point at which the first projected observable has emitted its last item. Here is my code sample:
import { from } from 'rxjs';
import { interval } from 'rxjs';
import { take } from 'rxjs/operators';
import { map, mergeMap, exhaustMap, delay, mergeAll } from 'rxjs/operators';
const getData = (param) => {
if (param == 'a') {
return interval(1000).pipe(
map(val => param + '-' + val.toString()),
take(4),
delay(1000)
)
}
else if (param == 'b') {
return interval(1000).pipe(
map(val => param + '-' + val.toString()),
take(4),
delay(7000)
)
}
}
// using exhaustMap
from(['a','b']).pipe(
exhaustMap(param => getData(param))
).subscribe(val => console.log(val));
The behavior of exhaustMap is once triggered it will execute its inner observable first until its completion and during that time emission from the source observable will be ignored. So in your case b will be ignored, when source emit b, exhaustMap is still not yet completed
Related
I want to combine observables as is done with the "merge" operator but I still want to be able to know which input observable emitted, is there a way to do that?
For example:
private result$ = merge(this.obs1$, this.obs2$).pipe(
scan((result, change) => index + change, 0),
shareReplay(1)
);
Here both values from obs1 and obs2 will go into the "change" variable in the scan function whenever any of the input observables emit, but if I had access to a projector function where I could mark the values from the input observables with different names I could then do different things in the following scan function depending on which input observable emitted. Other operators like CombineLatest or ForkJoin do not seem to be applicable here either as they require completion or emits from all input observables.
If you need to keep track of which input observable emitted, then you may need to add metadata to your source observable. Without knowing the context of how result$ is used, this is the best possible solution with the information given.
I would suggest adding an id property to each observable you need to keep track of. Then, you can use some strategy in your scan operator based on the ID.
Below is a simple example using an id for each source observable. In the scan operator you will see how my strategy changes based on the ID.
import { interval, merge, of } from "rxjs";
import { map, scan, shareReplay } from "rxjs/operators";
const obs1$ = interval(1000).pipe(map(i => ({ i, id: "obs1" })));
const obs2$ = interval(3000).pipe(map(i => ({ i, id: "obs2" })));
let index = 0;
const result$ = merge(obs1$, obs2$).pipe(
scan((result, change) => {
if (change.id === "obs1") {
return index + change.i;
}
if (change.id === "obs2") {
return index + change.i * 2;
}
}, 0),
shareReplay(1)
);
result$.subscribe(console.log);
https://stackblitz.com/edit/rxjs-as5ket
The library #react-rxjs/utils has a util named mergeWithKey that can be used like this:
import { Subject } from "rxjs"
import { scan, startWith } from 'rxjs/operators'
import { mergeWithKey } from '#react-rxjs/utils'
const inc$ = new Subject()
const dec$ = new Subject()
const resetTo$ = new Subject<number>()
const counter$ = mergeWithKey({
inc$,
dec$,
resetTo$,
}).pipe(
scan((acc, current) => {
switch (current.type) {
case "inc$":
return acc + 1
case "dec$":
return acc - 1
case "resetTo$":
return current.payload
default:
return acc
}
}, 0),
startWith(0),
)
The implementation is pretty straight-forward:
import { merge, Observable, ObservableInput, from, SchedulerLike } from "rxjs"
import { map } from "rxjs/operators"
/**
* Emits the values from all the streams of the provided object, in a result
* which provides the key of the stream of that emission.
*
* #param input object of streams
*/
export const mergeWithKey: <
O extends { [P in keyof any]: ObservableInput<any> },
OT extends {
[K in keyof O]: O[K] extends ObservableInput<infer V>
? { type: K; payload: V }
: unknown
}
>(
x: O,
concurrent?: number,
scheduler?: SchedulerLike,
) => Observable<OT[keyof O]> = (input, ...optionalArgs) =>
merge(
...(Object.entries(input)
.map(
([type, stream]) =>
from(stream).pipe(
map((payload) => ({ type, payload } as any)),
) as any,
)
.concat(optionalArgs) as any[]),
)
Is this what you needed?
I've two observables obs1 and obs2. I'm looking for an operator that emit when atleast one of them emit. combineLatest needs both of them should emit atleast once.
import { combineLatest, forkJoin, of, zip, } from 'rxjs';
import { map } from 'rxjs/operators';
const obs1 = of();
const obs2 = of(1,2,3);
let s = null;
s = combineLatest(obs1, obs2)
.pipe(map(([a, b]) => {
console.log(a, b);
})).subscribe();
I believe you are searching for merge.
const obs1$ = timer(1000).pipe(map(() => 'Observable 1 emitted'));
const obs2$ = timer(10000).pipe(map(() => 'Observable 2 emitted'));
merge(obs1$, obs2$).subscribe(console.log);
See Stackblitz
https://stackblitz.com/edit/rxjs-kp8od3?file=index.ts
I need to merge main stream with updates stream this way:
Main: ----A-------B-------------C---|--
Upd: -D-----E---------F---G--------|--
========================================
Result:----A--AE---B----BF--BG---C---|--
I.e., when Main emitted, the result should always be Main (or Main with empty Upd). When Upd was emitted without previous Main, it should be ignored. If Upd was emitted after Main, then they should be combined.
Consider this TypeScript code:
interface Item {
Id: number;
Data: string;
}
function mergeUpdates(main: Item[], upd: Item[]) {
if (!upd || upd.length === 0) {
return main;
}
const result = main;
// const result = {...main};
for (const updatedItem of upd) {
const srcIndex = result.findIndex(_ => _.Id === updatedItem.Id);
if (srcIndex >= 0) {
result[srcIndex] = updatedItem;
} else {
result.push(updatedItem);
}
}
return result;
}
const main$ = new Subject<Item[]>();
const upd$ = new Subject<Item[]>();
const result$ = combineLatest(main$, upd$).pipe( // combineLatest is wrong operator!
map(([main, upd]) => mergeUpdates(main, upd)));
$result.subscribe(r => console.log(r.map(_ => _.Data).join(',')));
main$.next([{Id:1, Data:'Data1'}, {Id:2, Data:'Data2'}]);
upd$.next([{Id:1, Data:'Updated1'}]);
upd$.next([{Id:1, Data:'Updated2'}]);
main$.next([{Id:1, Data:'Data1_Orig'}, {Id:2, Data:'Data2'}]);
// Expexted result:
// 'Data1,Data2'
// 'Updated1,Data2'
// 'Updated2,Data2'
// 'Data1_Orig,Data2'
The only solution I have in mind is to use 'combineLatest' and mark items in upd$ stream as processed, thus do not use it again when data from main$ emitted later. I believe this is not the best approach as it cause unwanted side effects.
Is there any better solution for this task?
Thank you in advance.
Here would be my approach:
main$.pipe(
switchMap(
mainValue => merge(
of(mainValue),
upd$.pipe(
map(updVal => mainValue + updVal)
)
)
)
)
switchMap - make sure the inner observable's emitted values will be combined with the latest outer value
merge(of(a), upd$.pipe()) - emit the main value first, then listen to any notifications upd$ emits and combine them with the current main value
If another outer value comes in, the inner subscriber will be unsubscribed, meaning that the upd subject won't have redundant subscribers.
Let's say I have two Observables A and B, and I want to combine them to produce this behaviour: the subscription combination returns only if A fires when a B has already been fired. It differs from the zip because I don't want any return if A has already been fired and then a B fires. In other words: ignores A until B fires then return on the next A, then ignore any other A until B fires ... so on
I believe you need withLatestFrom operator:
import { withLatestFrom, map } from 'rxjs/operators';
import { interval } from 'rxjs';
const obsA = interval(2000);
const obsB = interval(1000);
const resultingObs = obsB.pipe(
withLatestFrom(obsA),
map(([bValue, aValue]) => {
return aValue
})
);
// This should emit values from obsA only when obsB has been fired.
const subscribe = resultingObs .subscribe(val => console.log(val));
I've pairs of events: add1/add2/etc and remove1/remove2/etc. I'd like the following:
when an add1 is emitted on the stream
if DELAY transpires with no new add* emissions
emit remove1
if add* is emitted
emit remove1 for add1 immediately
emit remove* for add* after DELAY
This should continue for all emissions of add* on the stream.
Here's a test I've written using RxJS marble testing for this case:
import test from 'tape'
import { set, lensPath } from 'ramda'
import { TestScheduler } from 'rxjs/testing'
import hideAfterDelay from '../another/file'
import { actionCreators } from '../another/dir'
const prefix = 'epics -> notifications'
test(`${prefix} -> hideAfterDelay`, t => {
t.plan(1)
const scheduler = new TestScheduler(t.deepEqual)
const actionMap = {
a: createAddAction('hello!'),
b: createAddAction('goodbye!'),
x: actionCreators.notifications.remove('hello!'),
y: actionCreators.notifications.remove('goodbye!')
}
scheduler.run(({ cold, expectObservable }) => {
const actionStream = cold('a-------a-b-a------', actionMap)
const expected = '-----x-----x-y----x'
const actual = hideAfterDelay(5)(actionStream)
expectObservable(actual).toBe(expected, actionMap)
})
})
function createAddAction (name) {
const action = actionCreators.notifications.add(name)
const lens = lensPath(['payload', 'id'])
return set(lens, name, action)
}
I think the test is representative of the behavior I described above and that I want.
How can I write this observable? I've tried using timer and race but I haven't been able to get this working...
This is an epic using redux-observable, btw.
Using RxJS v6
Ok, I think I got a working solution using a closure and slightly modifying my test assertion.
First, the expected marble diagram should look like this
// input: a-------a-b-a------
// - expected: -----x-----x-y----x
// + expected: -----x----x-y----x
//
// Note above that the middle x and y emit at the same time as new
// `add*` actions on the source stream instead of one frame later
With that small change—which still feels consistent with my description in the question—I was able to get my test passing with the following:
import { of, timer, empty } from 'rxjs'
import { switchMap, mapTo, tap, merge } from 'rxjs/operators'
import { ofType } from '../operators'
import actionTypes from '../../actionTypes/notifications'
import { actionCreators } from '../..'
export default (delay = 3000) => actionStream => {
let immediateRemove
return actionStream.pipe(
ofType(actionTypes.ADD),
switchMap(action => {
let obs = empty()
if (immediateRemove) {
obs = of(immediateRemove)
}
const remove = actionCreators.notifications.remove(action.payload.id)
immediateRemove = remove
return obs.pipe(
merge(
timer(delay).pipe(
tap(() => {
immediateRemove = null
}),
mapTo(remove)
)
)
)
})
)
}
I've no idea if this is the best or right way to solve it, but I'm fairly certain it is a way.