observable icon indicating copy to clipboard operation
observable copied to clipboard

How to implement custom operators?

Open flensrocker opened this issue 1 year ago • 7 comments

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!)

flensrocker avatar Nov 09 '24 15:11 flensrocker

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.

domfarolino avatar Nov 10 '24 21:11 domfarolino

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.

benlesh avatar Nov 12 '24 16:11 benlesh

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?

bakkot avatar Nov 12 '24 16:11 bakkot

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!

flensrocker avatar Nov 12 '24 16:11 flensrocker

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

ducin avatar Nov 16 '24 22:11 ducin

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

LcsGa avatar Nov 17 '24 00:11 LcsGa

The .pipe method makes a lot of sense, but... this is actually universal, as absolutely any value could benefit from it (so by extension a pipemethod should exists on Object.prototype.pipe). In my opinion we should wait and rely on the pipe operator proposal instead.

lifaon74 avatar Mar 12 '25 13:03 lifaon74