All files / src watch.ts

100% Statements 67/67
100% Branches 20/20
100% Functions 5/5
100% Lines 67/67

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 892x   2x           2x 62x 62x 62x   62x   62x 136x 136x   136x 130x 130x 130x   134x 136x   62x 46x 46x 46x 46x 46x 46x   62x 16x 16x 16x 16x 16x 16x 2x 2x 2x 2x 16x   62x 108x 9x 9x 9x 9x   108x 1x 1x   106x 106x   106x   106x 106x 108x 5x 5x 5x 108x 106x 2x 2x   106x 106x 106x   106x 106x 108x 62x   62x   62x 62x  
import { batch, batchFlush, batchStart, tasks } from "./batch";
import { type Disposer, type Get, type Readable, type ReadableLike } from "./typings";
import { getReadable, unsubscribe } from "./utils";
 
export interface WatchEffect {
  (get: Get, dispose: Disposer): (() => void) | undefined | void;
}
 
export const watch = (effect: WatchEffect): Disposer => {
  let running: boolean | undefined;
  let disposed: boolean | undefined;
  let collectedDeps: Set<Readable> | undefined;
 
  let cleanupEffect: Disposer | null | undefined | void;
 
  const get: Get = ($?: ReadableLike): unknown => {
    const readable = getReadable($);
    if (!readable) return $;
 
    if (!collectedDeps?.has(readable)) {
      (collectedDeps ??= new Set()).add(readable);
      readable.onReaction_(subscription);
    }
 
    return readable.get();
  };
 
  const subscription = () => {
    if (!running) {
      unsubscribe(collectedDeps, subscription);
      collectedDeps?.clear();
    }
    tasks.add(runner);
  };
 
  const dispose = () => {
    disposed = true;
    tasks.delete(runner);
    effect = null as any; // enable gc
    unsubscribe(collectedDeps, subscription);
    collectedDeps = undefined;
    if (cleanupEffect) {
      const cleanup = cleanupEffect;
      cleanupEffect = null;
      batch(cleanup);
    }
  };
 
  const runner = () => {
    if (cleanupEffect) {
      const cleanup = cleanupEffect;
      cleanupEffect = null;
      batch(cleanup);
    }
 
    if (disposed) {
      return;
    }
 
    const isTopRunner = !running;
    running = true;
 
    const isBatchTop = batchStart();
 
    try {
      cleanupEffect = effect(get, dispose);
    } catch (e) {
      unsubscribe(collectedDeps, subscription);
      collectedDeps?.clear();
      throw e;
    } finally {
      if (disposed) {
        dispose();
      }
 
      if (isTopRunner) {
        running = false;
      }
 
      isBatchTop && batchFlush();
    }
  };
  runner.task_ = runner;
 
  runner();
 
  return dispose;
};