All files / core disposables.ts

96.66% Statements 29/30
100% Branches 10/10
83.33% Functions 5/6
100% Lines 25/25

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                            1067x 1067x 1067x   2x     3511x 1x 1x   3510x 3510x 3507x 3506x 3506x 3506x   3510x 3510x     1067x 1064x 1064x 1064x 3501x 3501x   3x     1064x 1064x 1063x        
import type { Unsubscribe } from './types';
 
/** Owns cleanup functions and releases them once in reverse registration order. @internal */
export interface DisposableScope {
  /** Adds a cleanup function, or executes it immediately when the scope is already disposed. */
  add(dispose: Unsubscribe): Unsubscribe;
  /** Removes and executes every owned resource. Repeated calls are no-ops. */
  dispose(): void;
  /** Reports whether the scope has released its resources. */
  readonly disposed: boolean;
}
 
/** Creates an idempotent cleanup scope for listeners, timers, and observers. @internal */
export function createDisposableScope(): DisposableScope {
  const disposers = new Set<Unsubscribe>();
  let disposed = false;
  return {
    get disposed() {
      return disposed;
    },
    add(dispose) {
      if (disposed) {
        dispose();
        return () => undefined;
      }
      let active = true;
      const owned = () => {
        if (!active) return;
        active = false;
        disposers.delete(owned);
        dispose();
      };
      disposers.add(owned);
      return owned;
    },
    dispose() {
      if (disposed) return;
      disposed = true;
      const errors: unknown[] = [];
      for (const dispose of [...disposers].reverse()) {
        try {
          dispose();
        } catch (error) {
          errors.push(error);
        }
      }
      disposers.clear();
      if (errors.length === 1) throw errors[0];
      if (errors.length > 1) throw new AggregateError(errors, 'Multiple resource cleanups failed.');
    },
  };
}