Skip to content
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

Sugar for making it ergonomic to lift errors and completions from inner observables #189

Open
Monkatraz opened this issue Dec 26, 2024 · 0 comments
Labels
possible future enhancement An enhancement that doesn't block standardization or shipping

Comments

@Monkatraz
Copy link

Monkatraz commented Dec 26, 2024

At my workplace, I've done a lot with observables, and have used a specific pattern fairly extensively that helps with errors and completion events from observables subscribed to by other observables. I'll start with a problematic example, using the API that our internal observables use (mostly zen-observable like):

// let's have some observables that fetch data from some API,
// in this case some usage limits for "basic" and "advanced"
// (also, ideally these would be ref-counted, but we'll ignore that)
const basicLimit = new Observable<number>((subscriber) => {
  // -snip-
})

const advancedLimit = new Observable<number>((subscriber) => {
  // -snip-
})

// now let's make an observable that returns data from both
const limits = new Observable<{ basic: number, advanced: number }>((subscriber) => {
  let basic: number | null = null
  let advanced: number | null = null
  
  function next() {
    if (basic !== null && advanced !== null) {
      subscriber.next({ basic, advanced })
    }
  }
  
  const onBasic = basicLimit.subscribe((value) => {
    basic = value
    next()
  })
  
  const onAdvanced = advancedLimit.subscribe((value) => {
    advanced = value
    next()
  })
  
  return () => {
    onBasic.unsubscribe()
    onAdvanced.unsubscribe()
  }
})

You may have already noticed an issue - if basicLimit or advancedLimit error or complete early, that isn't at all indicated to the observer of limits. We're just using the next callback. You can of course fix this, but it's tedious especially when working with a lot of nested ref-counted observables:

// you have to do this for every single subscription
const subscription = obs.subscribe({
  next: (x) => { /* ... */ },
  error: (err) => subscriber.error(err),
  complete: () => subscriber.complete(),
})

My solution was to add a new way of subscribing, called lift:

// desugars to the above code block
const subscription = obs.lift(subscriber, (x) => { /* ... */ })

This is just sugar. It subscribes in a way where error and complete are automatically passed to subscriber, but next is handled by the callback in the second argument. You can also pass an observer object where you can override the behavior of either next, error, or complete, but any you don't override are passed to subscriber.

I called it lift because its intended idea is that it "lifts" up inner errors or completions up automatically - there might even be many inner lifts. If you use this religiously in your library, errors from deeply subscribed observables get passed all the way up to consumers without a lot a boilerplate being needed. I personally found it really helpful.

Since it's just sugar, it's understandable if this is out-of-scope or otherwise not desirable for this proposal. I would like to at least make it known that this was helpful for me and others, and that keeping this bit of friction in mind while working on the proposal would be appreciated.

@domfarolino domfarolino added the possible future enhancement An enhancement that doesn't block standardization or shipping label Dec 26, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
possible future enhancement An enhancement that doesn't block standardization or shipping
Projects
None yet
Development

No branches or pull requests

2 participants