ReactiveX / ReactiveX/rxjs

Discussion: Async teardown and what to do about it.

Open
#6,446 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
31.7k
Forks
3k
PR merge metrics
No merged PRs in 30d

Description

WARNING: This is a brain dump and is in NO WAY reflective of any future plans for RxJS.

I just want to put this hear because sometimes people ask about it.

Currently RxJS has no mechanism for handling/waiting for teardown that is asynchronous. This doesn't come up very often, but I've hit it a few times at my day job, and I think it's worth discussing here.

Examples:
  • Unsubscribing from a remotely pushed stream may require waiting for confirmation before re-subscribing.
  • Tearing down a resource that may not be fully torn down until a later event. (Ignoring a promise resolution, noop-ing in an animation frame, etc)
  • Dealing with APIs such as PushSubscription
Some thoughts:

I'm mostly just getting my thoughts on this down, but I have I few ideas around how this could work in a non-breaking way for RxJS, however, even if it's non-breaking, it might be "new enough" we wouldn't want to surprise people with it in a minor release, I don't know.

  1. We could start supporting returning either void or Promise<any> from teardowns returned from the new Observable init, or passed to Subscription.prototype.add.
  2. We could then also provide a method on Subscription called like finally(cb) that returns a promise (because it's guaranteed to emit a value).
  3. We would need to make sure the finalize operators did not fire until after all async teardown had settled.
Concept using Subscription:
// assuming:
interface Resource {
  ondata: (e: DataEvent) => void;
  onclose: () => void;
  unsubscribe(): Promise<void>;
}

const source = new Observable(subscriber => {
   const resource = new Resource();
   resource.ondata = e => subscriber.next(e.data);
   resource.onclose = () => subscriber.complete();
   subscriber.add(() => resource.unsubscribe()); // Note the promise return here
});

const subscription = source.subscribe(console.log);

subscription.finally(() => { console.log('torn down') });
Other Considerations:
  • We've talked about moving toward AbortController/AbortSignal as a more universal cancellation mechanism, which also does not really support this. If we decide to handle this use case, it might preclude using that primitive.
  • We might want to delay the signal of complete or error until everything is torn down as well. At least in the case of the promise returned by forEach.
  • If we add a finally(cb) to Subscription. Then Subscription itself is really a then method away from being what is basically a cancellable promise. Hahaha 😄 . At that point, forEach may not have much value? Or maybe it's the other way around?
  • Promise may be the wrong type here, because the inevitability is someone will want to cancel their teardown. Then what? It's a tricky problem.
Concept using forEach and AbortSignal:

This is probably more pie in the sky: But in a world where we're using AbortSignal for cancellation, we could keep the same bit as above for the new Observable where we allow async functions in subscriber.add... however, we could have the promise returned by forEach await finalization before resolving/rejecting/finalizing. Just a thought.

Final thoughts

As I look at this problem, I do realize that there are a lot of corner cases that would be solved neatly if all notifications/signals in RxJS were scheduled on a micro task. Which is something I actually hate to admit. However, with regards to teardown, ideally that happens synchronously if possible.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

This is a design discussion about asynchronous teardown involving Observable, Subscription, finalize, forEach, and AbortSignal; no implementation files or tests are named. Start by reviewing those APIs and the existing teardown behavior, but the issue does not define a decided approach or concrete completion criteria.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
api
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
20/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.