98 lines
2.7 KiB
JavaScript
98 lines
2.7 KiB
JavaScript
import { Signal } from 'signal-polyfill';
|
|
|
|
const notifiedEffects = new Set();
|
|
let batchDepth = 0;
|
|
|
|
/**
|
|
* Runs the given function inside of a "batch" and calls any batched effects
|
|
* (those created with `batchEffect()`) that depend on updated signals
|
|
* synchronously after the function completes.
|
|
*
|
|
* Batches can be nested, and effects will only be called once at the end of the
|
|
* outermost batch.
|
|
*
|
|
* Batching does not change how the signal graph updates, or change any other
|
|
* watcher or effect system. Accessing signals that are updated within a batch
|
|
* will return their updates value. Other computations, watcher, and effects
|
|
* created outside of a batch that depend on updated signals will be run as
|
|
* usual.
|
|
*
|
|
* @param fn The function to run inside the batch.
|
|
*/
|
|
const batch = fn => {
|
|
batchDepth++;
|
|
try {
|
|
// Run the function to notifiy watchers
|
|
fn();
|
|
} finally {
|
|
batchDepth--;
|
|
if (batchDepth !== 0) {
|
|
return;
|
|
}
|
|
|
|
// Copy then clear the notified effects
|
|
const effects = [...notifiedEffects];
|
|
notifiedEffects.clear();
|
|
|
|
// Run all the batched effect callbacks and re-enable the watchers
|
|
let exceptions;
|
|
for (const {
|
|
computed,
|
|
watcher
|
|
} of effects) {
|
|
watcher.watch(computed);
|
|
try {
|
|
computed.get();
|
|
} catch (e) {
|
|
(exceptions ??= []).push(e);
|
|
}
|
|
}
|
|
if (exceptions !== undefined) {
|
|
if (exceptions.length === 1) {
|
|
throw exceptions[0];
|
|
} else {
|
|
throw new AggregateError(exceptions, "Multiple exceptions thrown in batched effects");
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates an effect that runs synchronously at the end of a `batch()` call if
|
|
* any of the signals it depends on have been updated.
|
|
*
|
|
* The effect also runs asynchronously, on the microtask queue, if any of the
|
|
* signals it depends on have been updated outside of a `batch()` call.
|
|
*
|
|
* @param effectFn The function to run as an effect.
|
|
* @returns A function that stops and disposes the effect.
|
|
*/
|
|
const batchedEffect = effectFn => {
|
|
const computed = new Signal.Computed(effectFn);
|
|
const watcher = new Signal.subtle.Watcher(async () => {
|
|
// Synchonously add the effect to the notified effects
|
|
notifiedEffects.add(entry);
|
|
|
|
// Check if our effect is still in the notified effects
|
|
await 0;
|
|
if (notifiedEffects.has(entry)) {
|
|
// If it is, then we call it async and remove it
|
|
notifiedEffects.delete(entry);
|
|
computed.get();
|
|
}
|
|
});
|
|
const entry = {
|
|
computed,
|
|
watcher
|
|
};
|
|
watcher.watch(computed);
|
|
computed.get();
|
|
return () => {
|
|
watcher.unwatch(computed);
|
|
notifiedEffects.delete(entry);
|
|
};
|
|
};
|
|
|
|
export { batch, batchedEffect };
|
|
//# sourceMappingURL=batched-effect.ts.js.map
|