-
Notifications
You must be signed in to change notification settings - Fork 79
Description
Commit
fb80e6a
Introduce setting a generator before running the next version in Runtime 6.0.0
However, this can clash with the common pattern of using Generator.input to extract a logical signal from a UI element (e.g. viewof)
If the UI is initialized to undefined the Generator never resolves. If the UI is then updated to expose a value, then the generator not resolving prevents it from ever generating a value, even though the UI form might be updating values and dispatching fresh events.
I noticed this because Plot can initialised with undefined values for pointer events and you might fill in data later, but then all the pointer values never update.
node.js reproduction (you have to toggle the runtime in the package.json, item get stuck in pending for runtime 6.0.0). Not sure where the best fix lies, maybe Generator.input can be fixed rather than mess with runtime? Or maybe runtime should have a timeout for long running Generators?
Further discussion on the feature was here
{
"name": "generator-bug-repro",
"type": "module",
"scripts": {
"test": "node index.mjs"
},
"dependencies": {
"//":"or set to runtime version 6 when it breaks",
"@observablehq/runtime": "5",
"@observablehq/stdlib": "^5.8.8"
}
}
// Generator bug repro - comparing Observable runtime v5.9.9 vs v6.0.0
// Run with: npm run test
// (after changing the runtime dependency version in package.json)
import { Runtime } from "@observablehq/runtime";
import { Library } from "@observablehq/stdlib";
const version = process.env.RUNTIME_VERSION || "unknown";
console.log(`Using Observable runtime v${version}\n`);
// Use the real stdlib Generators
const library = new Library();
const Generators = library.Generators;
// Minimal mock input element compatible with Generators.input
// (stdlib Inputs.input needs DOM, so we create a simple EventTarget-based mock)
function createMockInput(initialValue) {
const listeners = new Map();
return {
value: initialValue,
addEventListener(type, fn) {
if (!listeners.has(type)) listeners.set(type, []);
listeners.get(type).push(fn);
},
removeEventListener(type, fn) {
const arr = listeners.get(type);
if (arr) {
const idx = arr.indexOf(fn);
if (idx >= 0) arr.splice(idx, 1);
}
},
dispatchEvent(event) {
const arr = listeners.get(event.type);
if (arr) arr.forEach(fn => fn(event));
}
};
}
// Simple observer that logs to console
function observer(name) {
return {
pending: () => console.log(`[${name}] pending`),
fulfilled: (value) => console.log(`[${name}] fulfilled:`, value),
rejected: (error) => console.log(`[${name}] rejected:`, error)
};
}
// Define the notebook
function define(runtime, observer) {
const main = runtime.module();
// Provide builtins - using REAL Generators.input from stdlib
main.builtin("Generators", Generators);
main.builtin("createMockInput", createMockInput);
// viewof trigger - initial value "tick", changes to "tock" after 200ms
main.variable(observer("viewof trigger")).define("viewof trigger", ["createMockInput"], (createMockInput) => {
console.log(" _trigger function running");
const view = createMockInput("tick");
setTimeout(() => {
console.log(" trigger setTimeout firing -> tock");
view.value = "tock";
view.dispatchEvent({ type: "input" });
}, 200);
return view;
});
// trigger - generator that yields viewof trigger's value (using real Generators.input)
main.variable(observer("trigger")).define("trigger", ["Generators", "viewof trigger"], (G, _) => {
console.log(" trigger generator starting");
return G.input(_);
});
// viewof item - depends on trigger, initial undefined, dispatches "foo" after delay
let itemRunCount = 0;
main.variable(observer("viewof item")).define("viewof item", ["trigger", "createMockInput"], (trigger, createMockInput) => {
itemRunCount++;
const runNum = itemRunCount;
console.log(` _item function running #${runNum} (trigger = ${trigger})`);
const view = createMockInput(undefined);
// Only set timeout on the SECOND run (after trigger changes to "tock")
if (trigger === "tock") {
setTimeout(() => {
console.log(` item setTimeout #${runNum} firing -> foo`);
view.value = "foo";
view.dispatchEvent({ type: "input" });
}, 500);
}
return view;
});
// item - generator that yields viewof item's value (using real Generators.input)
main.variable(observer("item")).define("item", ["Generators", "viewof item"], (G, _) => {
console.log(" item generator starting");
return G.input(_);
});
// Display cell that shows item's value
main.variable(observer("display")).define("display", ["item"], (item) => {
console.log(" display function running (item =", item, ")");
return item;
});
return main;
}
// Run the notebook
console.log("Starting notebook...\n");
const runtime = new Runtime();
runtime.module(define, observer);
// Keep the process alive to see all the async behavior
setTimeout(() => {
console.log("\n--- Test complete (2.5s timeout) ---");
process.exit(0);
}, 2500);