tc39 / proposal-observable

Observables for ECMAScript
https://tc39.github.io/proposal-observable/
3.07k stars 90 forks source link

Even simpler API #204

Closed alshdavid closed 4 years ago

alshdavid commented 4 years ago

Philosophy

I believe the observable specification should be as simple as possible, aiming at replacing addEventListener and not rxjs.

While rxjs is a fantastic library, much like Bluebird, it includes features that can be implemented by the community.

We all want observables in the spec and too complex a proposal is also hard to get across the line.

Proposal:

interface Observable<T> {
    subscribe(cb: Callback<T>): Subscription
}

interface Subscription<T> {
    unsubscribe(): void
    catch(error: (error: any) => void): Subscription
    toPromise(): Promise<T>
    static CompleteToken: Symbol
}

type Callback<T>: (value: T) => void | Subscription.CompleteToken

type NextFn<T> = (value: T) => void
type CompleteFn = () => void
type CleanupFn = () => void

type ObservableConstructor<T> = (
    setup: (n: NextFn<T>, c: CompleteFn) => CleanupFn
) => Observable<T>

Usage

const onscroll$ = new Observable(next => {
    window.addEventListener('scroll', next)
    return () => window.removeEventListener('scroll', next)
})

const sub = onscroll$.subscribe(console.log)
sub.unsubscribe()

// Automatically unsubscribe when a CompleteToken is returned
onscroll$.subscribe(() => {
    console.log('Scrolled once')
    return Subscription.CompleteToken 
})

// Completion on a subscription triggers a promise
onscroll$
    .subscribe(() => Subscription.CompleteToken)
    .toPromise()
    .then(console.log)

Error handling

Propagate errors into an catch callback (like promises)

const source$ = new Observable(next => {
    throw new Error('Whoops!')
})

const sub = source$
    .subscribe(console.log)
    .catch(console.log)

sub.unsubscribe()
hax commented 4 years ago

This API seems close to Emitter proposal?

alshdavid commented 4 years ago

Not really. The proposal above is about a minimal feature-set to deal with event sources through use of a Observable type.

The second point was about how declarative reactive programming belongs as community driven extensions

I have now removed the second point as the operators are not included in my spec proposal

NOT PART OF SPEC

Community Driven Extension

Extended capabilities like externalising emitters, holding state and composing operators work just fine with the above proposal, they'd just need to be created by the community and provides as part of an external package.

Here are some syntax examples of how such a package could look

Not a real package

import { pipe, fromEvent } from 'extended-observables'
import { map, filter } from 'extended-observables/operators'

const oninput$ = fromEvent(inputElement, 'input')

onscroll$.subscribe(pipe(
    map(event => event.target.value),
    filter(value => value === 'foobar'),
    value => {
        console.log('it says foobar')
    }
))

And wrappers could be made using the internals

import { Subject, BehaviorSubject } from 'extended-observables''

And extend operators functionally

import { counter, pipe } from 'extended-observables'
import { tap, map, filter } from 'extended-observables/operators'

const counter$ = counter()

const a$ = pipe(
    tap(console.log)
)(counter$)

const b$ = pipe(
    map(i => i + 1), 
    map(i => i * 2)
)(a$)

const c$ = pipe(
    filter(i => i % 2)
)(b$)

c$.subscribe()

Can even work with the pipe operator

const counter$ = counter()

counter$.subscribe(i => i 
    |> tap(console.log)
    |> map(i => i + 1)
    |> map(i => i * 2)
    |> filter(i => i % 2)
    |> i => console.log('fin')
)
erykpiast commented 4 years ago

How mapping is handled in such API? If subscribe method returns Subscription, not an Observable, is that responsibility of the operator itself, to subscribe and return a new observable instance?

alshdavid commented 4 years ago

After some thought, I don't think I'm on the right track here.

Check this for my revised proposal: https://github.com/tc39/proposal-observable/issues/205