-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Need debounceMap (like exhaustMap, but plays the last event) #1777
Comments
Got a correct (but not perfect) implementation that might show better what i want: function auditMap(project, resultSelector) {
return Rx.Observable.create(ob => {
var count = 0;
return this.do(() => count++)
.concatMap((e, i) => Rx.Observable.defer(() =>
count === 1 ? project(e, i) : Rx.Observable.empty()
).concat(Rx.Observable.of(0).do(() => count--).ignoreElements()),
resultSelector
).subscribe(ob);
});
};
Rx.Observable.prototype.auditMap = auditMap; https://jsbin.com/galuno/6/edit?js,output The only problem with this solution is that it will keep all emitted values in memory until the inner observable completes. This is inherent to concatMap but would not be needed for this operator. Some expected in and outputs: result$ = source$.auditMap(e => Rx.Observable.timer(0, 3050).take(2).mapTo(e))
I named it |
I just realize i need this operator too but then as a normal one without selector, like debounce, throttle etc. For example, with a timeSpan of 3:
I do not believe there is any easy way to get these marbles with current operators. I also realize names like auditMap and debounceMap are not correct because they would have different marbles if used simply like |
For reference, here's my solution: function auditMap(project, resultSelector) {
return Observable.using(
() => ({
running$: new BehaviorSubject(false)
}),
({ running$ }) =>
this.audit(() => {
return running$
.first(running => !running)
.do(() => running$.next(true))
}).concatMap(
(value, index) => Observable.from(project(value, index))
.do(null, null, () => running$.next(false)),
resultSelector
)
)
} Not exactly pretty. I would prefer an operator in the library, or at least a nicer way of doing it using existing operators. There's currently (v5.4.1) some issues with |
Another idea on this subject i wrote up a while ago, posting it here to be able to find it back easier later.
The idea of the operator here is pretty much an |
Somebody just pointed out to me that |
Closing because:
|
However throttle does not have a map feature so it's not the same. |
Perform the mapping separately. Conflating the mapping and the time-based operation into a single, built-in operator doesn't seem like something that would make for an acceptable core library operator. Also:
|
Please show a trivial implementation of a mergemap like operator that will execute the first inner observable, but delay the second when the first one is still active when it arrives, or drops the second when a third arrives before the first one finished. For example this is how you would want exhaust map to behave to make it useful for things like type ahead, instead of switchmap. |
You would not want to use For background, read the article that's referenced in the comment at the top of that file. And note that the implementation is trivial. |
That's a variant of switchmap, the proposal here is to have a function that combines the useful properties of Namely: do not cancel ongoing requests (concat and exhaust) Notice switchmapuntil still cancels ongoing requests,so it's not suitable for all purposes. |
I see what you mean about non-cancellable requests. However, the debouncing should be done before the mapping, IMO. I would not favour relying upon Anyway, a user-land implementation is not complicated (almost anything can be built with function exhaustMapWithTrailing<T, R>(
project: (value: T, index: number) => ObservableInput<R>
): OperatorFunction<T, R> {
return source => {
let pending = false;
let queued: [T, number] | undefined = undefined;
return source.pipe(
mergeMap((value, index) => {
if (pending) {
queued = [value, index];
return EMPTY;
}
pending = true;
return from(project(value, index)).pipe(
concat(defer(() => {
if (!queued) {
return EMPTY;
}
const projected = project(...queued);
queued = undefined;
return from(projected);
})),
tap({
complete: () => pending = false
})
);
})
);
};
} |
We certainly don't have the same definition of a trivial implementation. What I think is trivial/common here is the use case leading to the need of that operator: "discard all subsequent but hold most recent". |
I'm going to leave this here as it's been 2 days and i have not gotten around to test this, i wonder if the following scenario will pass in source.pipe(
exhaustMapWithTrailing(e => of(e).pipe(
delay(30)
))
);
|
@Dorus I just tested it and 3 does not get emitted, unfortunately :( Edit: I wrote one, kinda messy, could probably be optimized:
|
http://i.imgur.com/ciO6Kxd.png
It would be nice to have an operator that plays the latest event, like
exhaustMap
, but also guarantees that the last event from a stream is processed.A good usecase would be when inner the observable does not support cancellation.
switchMap
would not be suited because all inner sources would stay active in the background. Instead you might want to useexhaustMap
, but then we might miss the latest event. A new operator that would process the last event too would be useful.The text was updated successfully, but these errors were encountered: