Commissioned for this museum · after Gamma, Helm, Johnson & Vlissides, 1994
Observer
TypeScript·1994·31 lines·891 bytes
/**
* Observer — a subject keeps a list of dependants and tells them all when
* something changes.
*/
type Unsubscribe = () => void;
class Subject<T> {
private readonly observers = new Set<(value: T) => void>();
subscribe(observer: (value: T) => void): Unsubscribe {
this.observers.add(observer);
// The subscription hands back its own undoing. The caller never needs
// to hold on to the observer to be able to detach it.
return () => {
this.observers.delete(observer);
};
}
notify(value: T): void {
// Iterate a copy: an observer is allowed to unsubscribe itself while
// being notified, and that would otherwise mutate the set mid-loop.
for (const observer of [...this.observers]) {
observer(value);
}
}
}
const temperature = new Subject<number>();
const stop = temperature.subscribe((deg) => console.log(`${deg}°`));
temperature.notify(21);
stop();Curator’s note
Every event system you have ever used is this, wearing different clothes. DOM listeners, React state, message queues, spreadsheet cells that recalculate when you edit a neighbour — all of it is a subject holding a list of dependants and walking the list when something changes.
Two lines here are doing the real work. subscribe returns the function that undoes it, so a caller can detach without keeping a reference to the handler it passed in; this is why useEffect cleanup looks the way it does. And notify iterates [...this.observers] rather than the set itself, because an observer is entitled to unsubscribe *during* its own notification, and mutating a set you are looping over is how event systems corrupt themselves.
What the pattern hides is ordering. The Set preserves insertion order, so observers fire in the order they registered — a fact nothing in the type signature promises and which people nonetheless come to depend on. Almost every production bug in observer code is really a bug about ordering, or about a handler that throws and takes the rest of the list down with it.
Notice this file never mentions what it is observing. That is the whole trick: the subject knows how many dependants it has and nothing whatsoever about them.