Public
Edited
Feb 20, 2024
Insert cell
Insert cell
Insert cell
function* mapGradually(iterable, mapper, batch = defaultBatch) {
const progress = document.createElement("progress");
progress.max = iterable.length;
const label = document.createElement("label");
const text1 = document.createTextNode("0");
const text2 = document.createTextNode(`/${iterable.length ?? "?"}`);
const final = document.createElement("div");
label.appendChild(progress);
label.appendChild(text1);
label.appendChild(text2);
final.appendChild(label);
const { promise, resolve } = Promise.withResolvers();
final.value = promise;
yield final;
const output = [];
let i = 0;
while (i < iterable.length) {
let result = iterable[i];
if (mapper) result = mapper(iterable[i]);
output.push(result);
++i;
if (i % batch === 0) {
if (iterable.length) progress.value = i;
text1.data = i;
yield final;
}
}
resolve(output);
}
Insert cell
viewof test = mapGradually(_.range(testSize), (x) => x * 2)
Insert cell
test
Insert cell
Insert cell
function* mapGradually1(iterable, mapper, batch = defaultBatch) {
const length = iterable.length;
const progress = document.createElement("progress");
if (length) progress.max = length;
const label = document.createElement("label");
const text1 = document.createTextNode("0");
const text2 = document.createTextNode("/" + (iterable.length ?? "?"));
label.appendChild(progress);
label.appendChild(text1);
label.appendChild(text2);
const { promise, resolve } = Promise.withResolvers();
yield label;

const output = [];
let i = 0;
for (const it of iterable) {
output.push(mapper ? mapper(it) : it);
++i;
if (i % batch === 0) {
if (length) progress.value = i;
text1.data = i;
yield label;
}
}
text2.data = "/" + i;

progress.max = i;
progress.value = i;
label.value = output;
yield label;
}
Insert cell
viewof test1 = mapGradually1(_.range(testSize), (x) => x * 2)
Insert cell
test1
Insert cell
viewof test2 = mapGradually1(
(function* () {
let i = 0;
while (i < testSize) yield i++;
})(),
(x) => x * 2
)
Insert cell
test2
Insert cell
viewof test3 = mapGradually1(_(_.range(testSize)), (x) => x * 2)
Insert cell
Insert cell
function mapGradually2(iterable, mapper, batch = defaultBatch) {
const length = iterable.length;
const progress = DOM.element(
"progress",
length ? { max: length } : undefined
);
const label = DOM.element("label");
const text1 = DOM.text("0");
const text2 = DOM.text("/" + (iterable.length ?? "?"));
label.appendChild(progress);
label.appendChild(text1);
label.appendChild(text2);
const view = { value: label, done: false };

const output = [];
const doneResult = { value: output, done: true };
let i = 0;
const it = iterable[Symbol.iterator]();

const ourIt = {
next: start, // functions declared below
return: () => ((ourIt.next = finished), it.return?.() ?? doneResult)
};
return ourIt;

function start() {
ourIt.next = process;
return view;
}
function process() {
let value, done;
while ((({ value, done } = it.next()), !done)) {
output.push(mapper ? mapper(value) : value);
if (++i % batch === 0) {
if (length) progress.value = i;
text1.data = i;
return view;
}
}

// Done!
text1.data = i;
text2.data = "/" + i;
progress.value = i;
progress.max = i;
label.value = output;

ourIt.next = finished;
return view; // one last time
}
function finished() {
return doneResult;
}
}
Insert cell
viewof test4 = mapGradually2(_.range(testSize), (x) => x * 2)
Insert cell
test4
Insert cell
viewof test5 = mapGradually2(
(function* () {
let i = 0;
while (i < testSize) yield i++;
})(),
(x) => x * 2
)
Insert cell
test5
Insert cell
viewof test6 = mapGradually2(_(_.range(testSize)), (x) => x * 2)
Insert cell
test6
Insert cell
Insert cell
rx = import("https://unpkg.com/rxjs@7.8.1/dist/esm/index.js?module")
Insert cell
// Full Rx
rx.lastValueFrom(
rx.from(_.range(testSize)).pipe(
rx.map((x) => 2 * x),
rx.toArray()
)
)
Insert cell
{
// Manual subscription
const arr = [];
rx.from(_.range(testSize))
.pipe(rx.map((x) => 2 * x))
.subscribe((x) => arr.push(x));
return arr;
}
Insert cell
// Manual notification
rx.lastValueFrom(
new rx.Observable(function subscribe(subscriber) {
const data = _.range(100); // lower number since only one is processed each frame
const it = data.values();

// Keep track of the interval resource
const intervalId = setInterval(() => {
const { value, done } = it.next();
if (done) {
subscriber.complete();
unsubscribe();
} else {
subscriber.next(value);
}
}, 0);

// Provide a way of canceling and disposing the interval resource
function unsubscribe() {
clearInterval(intervalId);
}
// Auto-cancel when cell input is invalidated
invalidation.then(unsubscribe);
return unsubscribe;
}).pipe(
rx.map((x) => 2 * x),
rx.toArray()
)
)
Insert cell
Insert cell
// Custom observable to reduce buffering and scheduler overhead
function staggeredFrom(iterable, notify = () => {}, timeBudgetMs = 15) {
return new rx.Observable(function subscribe(subscriber) {
const it = iterable[Symbol.iterator]();
let count = 0;
function produce() {
const deadline = Date.now() + timeBudgetMs;
while (Date.now() < deadline) {
const { value, done } = it.next();
if (done || subscriber.closed) {
cleanup();
return;
}
subscriber.next(value);
count++;
}
notify(count);
requestAnimationFrame(produce);
}
requestAnimationFrame(produce);
function cleanup(reason) {
notify(count);
if (!subscriber.closed) {
if (reason) subscriber.error(reason);
else subscriber.complete();
}
}
invalidation.then(cleanup);
return cleanup;
});
}
Insert cell
// Helper to make a view
function progressView() {
const el = htl.html`<label>
<progress></progress>
<b>0</b>/<b>?</b>
</label>`;
const [t1, t2] = el.getElementsByTagName("b");
const progress = el.firstElementChild;
return {
el,
updateCount(value) {
progress.value = value;
t1.textContent = value;
},
set total(value) {
progress.max = value;
t2.textContent = value;
},
set value(value) {
el.value = value;
el.dispatchEvent(new Event("input", { bubbles: true }));
}
};
}
Insert cell
function stagger(source, ...operators) {
const length = source.length;

const view = progressView();
if (length) view.total = length;
const producer = staggeredFrom(source, view.updateCount).pipe(...operators);

const output = [];
const unsub = producer.subscribe({
next: (value) => output.push(value),
error: (err) => (view.value = err),
complete: () => (view.value = output.length === 1 ? output[0] : output)
});
invalidation.then(unsub);
return view.el;
}
Insert cell
viewof test7 = stagger(
_.range(testSize * 100),
rx.map((x) => 2 * x)
)
Insert cell
test7
Insert cell
Insert cell
{
function* duplexMap(f) {
let value = yield;
while (true) {
value = yield f(value);
}
}
const gen = duplexMap((x) => 2 * x);
gen.next(); // advance to first yield
return [
gen,
gen.next(1),
gen.next(2),
gen.next(),
gen.return(3),
gen.next(4)
];
}
Insert cell
{
function* duplexMap(f) {
let value = yield;
try {
while (value != 42) {
value = yield f(value);
}
} finally {
return f(value);
}
}
const gen = duplexMap((x) => 2 * x);
gen.next();
const gen2 = duplexMap((x) => 2 * x);
gen2.next();

return [
[gen, gen.next(1), gen.next(2), gen.return(3), gen.next()],
[gen2.next(1), gen2.next(2), gen2.next(42)]
];
}
Insert cell
Insert cell
{
const f = (x) => 2 * x;
const gen = {
next(input) {
return f(input);
}
};
}
Insert cell
Insert cell
custom = ({
mapper: {},
filterer: {},
reducer: {}
})
Insert cell
Insert cell
Reflect.getOwnPropertyDescriptor([1], "length")
Insert cell
new Proxy([1], {
get(it, key) {
if (key === "length") return 10;
return it[key];
}
})
Insert cell
({ length: 5, 1: 1 })
Insert cell
Insert cell
defaultBatch=1000
Insert cell
testSize = 10000
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