Public
Edited
Jan 6, 2024
Insert cell
Insert cell
Insert cell
// An implementation of the observer pattern
class Observable {
constructor(initialValue) {
this._value = initialValue;
this.observers = [];
}

read() {
return this._value;
}

write(newValue) {
if (newValue !== this._value) {
this._value = newValue;
this.notifyObservers();
}
}

// Method to add an observer
subscribe(observer) {
this.observers.push(observer);
}

// Method to notify all observers about the value change
notifyObservers() {
this.observers.forEach(observer => observer(this._value));
}
}
Insert cell
function combineLatest(...inputs) {
const observables = inputs.slice(0, -1);
const fn = inputs[inputs.length - 1];

/* NEW! */
// store the latest value for each observable
let latestValues = observables.map(obs => obs.read());

// initialize a new output observable
const obs = new Observable(fn(...latestValues));

// every time an observable changes...
observables.forEach((observable, index) => {
observable.subscribe((value) => {
/* NEW! */
// push the update to latestValues
latestValues[index] = value;

// update the output observable
obs.write(fn(...latestValues));
});
});

return obs;
}
Insert cell
lastCommaFirst = new Observable("Pollock, Josh")
Insert cell
first = combineLatest(lastCommaFirst, (lastCommaFirst) => lastCommaFirst.split(',')[1].trim())
Insert cell
last = combineLatest(lastCommaFirst, (lastCommaFirst) => lastCommaFirst.split(',')[0].trim())
Insert cell
greetingSplit = combineLatest(first, last, (first, last) => `Hello ${first} ${last}!`)
Insert cell
// we want to replace this with an effect
greetingSplit.subscribe((greetingSplit) => console.log(`greetingSplit: ${greetingSplit}`))
Insert cell
EffectQueue = []
Insert cell
function combineLatestEffect(...inputs) {
const observables = inputs.slice(0, -1);
const fn = inputs[inputs.length - 1];

// store the latest value for each observable
let latestValues = observables.map(obs => obs.read());

// effects only run when we tell them to!
const effect = { fn, latestValues };
EffectQueue.push(effect);

// every time an observable changes...
observables.forEach((observable, index) => {
observable.subscribe((value) => {
// push the update to latestValues
latestValues[index] = value;
if (!EffectQueue.includes(effect))
EffectQueue.push(effect);
});
});
}
Insert cell
function stabilize() {
for (const effect of EffectQueue) {
effect.fn(...effect.latestValues)
}
EffectQueue.length = 0; // empty the queue
}
Insert cell
combineLatestEffect(greetingSplit, (greetingSplit) => console.log(`greetingSplitEffect: ${greetingSplit}`))
Insert cell
EffectQueue
Insert cell
// When you submit a name in this field, it will be written to the `name` Observable.
// If you only change the whitespace, you won't see a new console log!
viewof updatedName = Inputs.text({value: "name, your", placeholder: "Enter your last, first name", submit: true, autocomplete: false})
Insert cell
lastCommaFirst.write(updatedName)
Insert cell
// run this cell to run the effects!
stabilize()
Insert cell
logTable(10)
Insert cell
Insert cell
eagerEffects = {
let EffectQueue = [];
let stabilizationQueued = false;

function stabilize() {
for (const effect of EffectQueue) {
effect.fn(...effect.latestValues)
}
EffectQueue.length = 0; // empty the queue
}

function deferredStabilize() {
if (!stabilizationQueued) {
stabilizationQueued = true;
queueMicrotask(() => {
stabilizationQueued = false;
stabilize();
});
}
}

function combineLatestEffectEager(...inputs) {
const observables = inputs.slice(0, -1);
const fn = inputs[inputs.length - 1];
// store the latest value for each observable
let latestValues = observables.map(obs => obs.read());
// effects only run when we tell them to!
const effect = { fn, latestValues };
EffectQueue.push(effect);
deferredStabilize();
// every time an observable changes...
observables.forEach((observable, index) => {
observable.subscribe((value) => {
// push the update to latestValues
latestValues[index] = value;
if (!EffectQueue.includes(effect)) {
// console.log('Queueing', effect)
EffectQueue.push(effect);
deferredStabilize();
}
});
});
}

return {
EffectQueue,
stabilizationQueued,
stabilize,
deferredStabilize,
combineLatestEffectEager
}
}
Insert cell
combineLatestEffectEager = eagerEffects.combineLatestEffectEager
Insert cell
combineLatestEffectEager(greetingSplit, (greetingSplit) => console.log(`greetingSplitEffectEager: ${greetingSplit}`))
Insert cell
logTable(10)
Insert cell
/* notes on how to do effects without queuemicrotask

- push to effect queue like before
- ????


once we know an effect is dirty, we can run it assuming we can pull values. but if we don't have the pull behavior, then we can't do this trick

suggests maybe we should tackle cache first...

*/
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more