WICG / observable

Observable API proposal
https://wicg.github.io/observable/
Other
592 stars 16 forks source link

How to implement custom operators? #182

Open flensrocker opened 2 weeks ago

flensrocker commented 2 weeks ago

My context: Angular application developer since v2, so I have seen various versions of rxjs etc. (but also forgot how it worked in the past).

In my applications I tend to write some custom operators to encapsulate some reusable behaviour. How is the implementation story with this API?

Extending the prototype (something I'm not very familiar with) seems to be a source of conflict, if different libraries would like to provide custom operators.

rxjs introduced the pipe function to easily chain operators to new "higher" operators, which also helps me as a TypeScript user to get the right types further down the chain.

(Thanks for this API to all involved. I really appreciate this and am looking forward to finally be able to combine different libraries with their different observable implementations!)

domfarolino commented 1 week ago

I would love @benlesh's thoughts on this, as I learned about the possibly type issues with what was my default understanding of how developers would solve this: by patching the prototype.

benlesh commented 1 week ago

RxJS is already moving this direction. Version 8 (still alpha) added an rx function that effectively is just this:

function rx(source, ...fns) {
  return from(source).pipe(...fns);
}

So with this, RxJS 8 (once using native observable types) will have a function that is implemented like so:

function rx(source, ...fns) {
  const o = Observable.from(source);

  return fns.reduce((prev, fn) => fn(prev), o);
}

This allows you to write functions in the "pipeable" way and use them.

function mapTwice(fn) {
  return (source) => source.map(fn).map(fn)
}

function nevermindJustComplete() {
  return (source) => new Observable(subscriber => {
    source.subscribe({ next: () => subscriber.complete() }, { signal: subscriber.signal });
  });
}

rx(someObservable, mapTwice(x => x + x), nevermindJustComplete())

Of course, the native functions will need pipeable analogs, which a library like RxJS can provide.

bakkot commented 1 week ago

For iterator helpers, I'm considering proposing an Iterator.prototype.into which is literally just Iterator.prototype.into = function (f) { return f(this); }.

Then you could do

array.values()
  .map(whatever)
  .into(function*(iter) {
    for (let item of iter) {
      yield item + 1;
    }
  })
  .filter(etc)

(Or .into any other function which takes an iterator or iteraable, of course.)

Something to consider here, maybe?

flensrocker commented 1 week ago

Of course, the native functions will need pipeable analogs, which a library like RxJS can provide.

Or should they be static functions on Observable? Or would that be too confusing as it makes not really sense to provide them on the platform?

Looking forward to rxjs 8! 🙂 Since I will use rxjs anyway, this will be ok for me.

Thank you!

ducin commented 1 week ago

How about providing a pipe method to the built-in API in order to make seamless extensibility with the ecosystem?

import { operatorX as operatorXLibA } from 'libA'
import { operatorX as operatorXLibB } from 'libB'

array.values()
  .map(whatever) // native
  .filter(whatever) // native
  .pipe(
    // either, or, or even both:
    operatorXLibA(whatever),
    operatorXLibB(whatever)
  )
  .map(whatever) // native
LcsGa commented 6 days ago

How about providing a pipe method to the built-in API in order to make seamless extensibility with the ecosystem?

import { operatorX as operatorXLibA } from 'libA'
import { operatorX as operatorXLibB } from 'libB'

array.values()
  .map(whatever) // native
  .filter(whatever) // native
  .pipe(
    // either, or, or even both:
    operatorXLibA(whatever),
    operatorXLibB(whatever)
  )
  .map(whatever) // native

I'd like that too! Basic operators could be within the prototype + another method would let us create and use our own operators like we would with the rxjs pipe