Closed tom-sherman closed 1 year ago
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.
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:
(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>
}
interface Observer<T> {
hasNext(notDone: boolean): void;
next(value: T): void;
}
interface Observable<T> {
observer(observer: Observer<T>): void;
}
notDone
, because that's weirdinterface Observer<T> {
complete(done: boolean): void;
next(value: T): void;
}
interface Observable<T> {
observer(observer: Observer<T>): void;
}
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;
}
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 methodlet'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;
}
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;
}
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.
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".