WICG / observable

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

Is `Observable` the right name? #31

Closed tom-sherman closed 1 year ago

tom-sherman commented 1 year ago

Now this is a kind of crazy question maybe but thought I'd ask it, I understand Observable is a very common name in the community right now - but is it the right one?

Iterable, Thenable, Iterator, generator - these are all protocols or interfaces. It could be argued, or mistaken by a newcomer, that Observable fits into this category - a protocol or interface that allows something to be "observed".

benlesh commented 1 year ago

Naming

I understand Observable is a very common name in the community right now - but is it the right one?

Firmly, yes. This is the name for this exact type across dozens of popular languages (JavaScript, C#, Python, C++, Swift, Kotlin, Java, Ruby, etc, etc). As a defined primitive, it's the name for the type. Very well known and understood.

It could be argued that the interface is a "Subscribable" or something like that... but the type itself is idiomatically called an "Observable" across millions and millions of lines of code in many languages.

Observable as a "dual" of Iterable

Iterable, Thenable, Iterator, generator - these are all protocols or interfaces. It could be argued, or mistaken by a newcomer, that Observable fits into this category

They would argue correctly. At least in the case of Iterable. Which is the "dual" of the Observable interface. There's a lot of talks given about this, but I can deconstruct it here... basically take an Iterable and pull it inside out:

We start with Iterable/Iterator

(I'm using a generic non-JS iterable/iterator pattern below, since JS is pretty avante garde in how iteration works, allocating an object every turn and having a possible "yes I'm done and here's a value" state that is generally ignored in most means of consumption)

interface Iterator<T> {
  hasNext(): boolean;
  next(): T;
}

interface Iterable<T> {
  iterator(): Iterator<T>
}

We then push where we were pulling before:

interface Observer<T> {
  hasNext(notDone: boolean): void;
  next(value: T): void;
}

interface Observable<T> {
  observer(observer: Observer<T>): void;
}

We fix the negative notDone, because that's weird

interface Observer<T> {
  complete(done: boolean): void;
  next(value: T): void;
}

interface Observable<T> {
  observer(observer: Observer<T>): void;
}

Which can be fixed further

It doesn't make sense to call complete(false) on every turn. So instead we can just call complete() once and assume that meansit's complete.

interface Observer<T> {
  complete(): void;
  next(value: T): void;
}

interface Observable<T> {
  observer(observer: Observer<T>): void;
}

Error handling

With an iterator, calling next() can throw an error you can catch... but when you're pushing values, it's going to be asynchronous, so we need a different way to communicate that, let's add an error(err: any) channel:

interface Observer<T> {
  complete(): void;
  next(value: T): void;
  error(err: any): void;
}

interface Observable<T> {
  observer(observer: Observer<T>): void;
}

observer isn't a good name for a method

let's call it subscribe. (idiomatically)

interface Observer<T> {
  complete(): void;
  next(value: T): void;
  error(err: any): void;
}

interface Observable<T> {
  subscribe(observer: Observer<T>): void;
}

Cancellation is different

With an Iterator, you can cancel iteration simply by breaking a loop or no longer iterating. With an observable that's different. There are several ways to do this (add/remove registrations, cancellation tokens, or returned cancellation mechanisms like subscriptions)... for this design we're just going to leverage the return value of the subscribe method to return a cancellation mechanism which is a subscription:

interface Observer<T> {
  complete(): void;
  next(value: T): void;
  error(err: any): void;
}

interface Observable<T> {
  subscribe(observer: Observer<T>): Subscription;
}

interface Subscription {
  unsubscribe(): void;
}
domfarolino commented 1 year ago

Given the really superb explanation here by @benlesh (thanks!) and the fact that if we're being honest, we're probably unlikely to re-litigate the name of this fairly well-known interface, I think I'll close now, especially since we have the reasoning all documented here for other people to see in the future here.