-
Notifications
You must be signed in to change notification settings - Fork 3k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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
Using Observables to cancel Observables. (Alternative to Abort Signal) #5683
Comments
Hmm, this is interesting - some thoughts on this:
Tangent:
For what it's worth when promises needed to do this we used callbacks which was both the shortest and had no error handling / timing guarantees that had to be upheld. In your case that would be: const source1$ = new Observable((subscriber, teardown) => {
const resource = new ExpensiveResource();
teardown(() => resource.close());
});
// which is almost as short, and provides no benefit over the 'current'
const source1$ = new Observable((subscriber, teardown) => {
const resource = new ExpensiveResource();
return () => resource.close();
}); We mainly did this because cancellation (in bluebird) was an afterthought of the promise constructor which already had other guarantees and constraints. |
Also:
I would very much be interested in exploring Observables in the platform again (as a much simpler primitive). I had to learn a lot just to get a (pretty basic) understanding of |
Interesting idea. I like it. One observation is that using a What would the implementation of |
I guess a BehaviorSubject might model that better. 🤔 |
Exciting! I am exploring this idea too a couple of months ago (check out the article), my thoughts: The foundation that we currently have is that Observable has two fundamental properties: data propagation and cancelation. It is evident that data propagation is fundamental since it is what makes an Observable to be observable, it is its way to express something to the subscriber and we achieve it using a callback @benlesh will the idea of accepting another Observable inside the producer function as a way for cancelation is open for the possibility to be more generic like what you have said:
If so then the API that I am imagining: Observable.CANCEL = Symbol('CANCEL');
const obs = new Observable((subscriber, talkback) => {
const id = setInterval(() => subscriber.next('hey'), 500);
talkback.pipe(
filter(token => token === Observable.CANCEL),
takeWhile(token => token !== Observable.CANCEL, true)
).subscribe(() => {
clearInterval(id);
subscriber.complete();
});
})
const cancel = fromEvent(document.body, "click").pipe(mapTo(Observable.CANCEL))
obs.subscribe(data => console.log(data), cancel); Then maybe we can extends the Observable to have a boilerplate encapsulation for automatic cancelation like: // CancelObservable extends Observable
new CancelObservable((subscriber, cancel) => {
cancel.subscribe(); // filtered subscriber request down to Observable.CANCEL only
}) I hope that RxJS team will explore this idea, it is very exciting! It will open up many opportunities. |
I'd like to iterate that supporting/using AbortSignals would not mean passing the signal to the subscriber function (in the observable constructor). It would just mean subscribe accepting an optional signal second argument and if a signal is passed there invoking the unsubscribe function const source1$ = new Observable((subscriber) => {
const resource = new ExpensiveResource();
// call next somewhere
return () => resource.close();
});
const unsubscrie = source1$.subscribe(...); // today, still works
source1$.subscribe(..., signal); // I am suggesting we add support for this This would break very little existing code while enabling interop with all other async code :] |
Bare with me cause I'm probably missing the point here... But out of curiosity, why introduce a new cancellation mechanism when rx already has one? source$.pipe(
abortWhen(abortSignal)
).subscribe({
next: console.log,
complete: () => console.log('not called on abort'),
error: e => console.error('called on abort', e);
}); This way, upstream is cancelled the normal way (returned function from subscribe function is called). |
Ok I indeed missed the point. Turns out (for me) that the plan was altogether to move away from old way of cancellation to a new way. #5863. As I understand to be able to support aborting of synchronously emitting observables (like in the firehose example), you need to be able to setup cancellation ahead of time. |
@benjamingr const firehose$ = new Observable((subscriber) => {
let cancelled = false;
let count = 0;
while(!cancelled) {
subscriber.next(count++);
}
return () => cancelled = true;
}); In this synchronous 'firehose' example, the subscriber function will never reach the point where the cancellation function is returned. Hence it can never be cancelled. const firehose$ = new Observable((subscriber, signal) => {
let count = 0;
while(!signal.aborted) {
subscriber.next(count++);
}
}); This is because cancellation is setup prior to calling the synchronous subscription function. Where in the first example, cancellation is (tried to be) setup after calling it. Still, my opinion is that cancellation in the first example should still be supported (as well), because I'd say that in more than 99% of the cases, observables are used in asynchronous ways, and as you say, this way it can be backward compatible with existing code. |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
Okay, this one might sound a little strange, but I was talking with @benjamingr, and he reminded me of past discussions about just using Observable as a signal by talking about what a shame it was we had
AbortSignal
natively, but noObservable
. Like "what if we wanted to signal something other than cancellation?".The idea
To that end, I'd like to re-explore this idea. Currently, we have the idea of
takeUntil
as an operator, which is effectively cancellation of the source observable, and notification of completion downstream. But what if we allowed that cancellation notification at the point ofsubscription
, and we also provided an observable of cancellations to the observable initializer:These two things would be very similar, only one would't cause the completion notification:
Let's take the example from #5649:
As you can see, the API difference is minimal.
Furthermore, we could easily provide something to get an
Observable
from anAbortSignal
, and vice versa. AND we could even allow for users to passAbortSignal
there, if it's available, and we could do the conversion for them under the hood.I think this solves a few problems around
AbortSignal
, in particular, if someone wanted to use the providedteardown$
to register teardowns, it's MUCH more ergonomic thanAbortSignal
:AbortSignal
Observable
It's a lot cleaner. I think the confusing bit above might be that users will wonder if they need to unsubscribe from the teardown they've subscribed to. But that's unnecessary, because teardown must happen eventually. Unless the subscription runs forever.
Pros
AbortSignal
doesAbortSignal
Cons
Other thoughts
If
Promise
was designed better, and allowed sync emission, it would have been better for this particular use case. Alas. We have what we have.The text was updated successfully, but these errors were encountered: