8000 Sugar for making it ergonomic to lift errors and completions from inner observables · Issue #189 · WICG/observable · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
Sugar for making it ergonomic to lift errors and completions from inner observables #189
Open
@Monkatraz

Description

@Monkatraz

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    possible future enhancementAn enhancement that doesn't block standardization or shipping

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      0