← Lazy initialization lesson
Lab

Lazy initialization

type Sheet = { id: number; species: string };

// The night's care-sheet export: still syncing when the desk opens.
let exportReady = false;

// The recipe: each word of a species name → the ids of its sheets.
function buildIndex(sheets: Sheet[]): Map<string, number[]> {
  if (!exportReady) throw new Error('export still syncing');
  const index = new Map<string, number[]>();
  for (const sheet of sheets) {
    for (const word of sheet.species.toLowerCase().split(' ')) {
      index.set(word, [...(index.get(word) ?? []), sheet.id]);
    }
  }
  return index;
}

// One center's care sheets. The lookup waits for the first lookup.
class CareSheets {
  builds = 0;
  private sheets: Sheet[];
  private index: Map<string, number[]> | undefined;

  constructor(sheets: Sheet[]) {
    this.sheets = sheets;
  }

  browse(): Sheet[] {
    return this.sheets;
  }

  lookup(word: string): number[] {
    this.index ??= this.build();
    return this.index.get(word) ?? [];
  }

  private build(): Map<string, number[]> {
    this.builds++;
    return buildIndex(this.sheets);
  }
}

const center = new CareSheets([
  { id: 1, species: 'Red fox' },
  { id: 2, species: 'River otter' },
  { id: 3, species: 'Barn owl' },
  { id: 4, species: 'Hedgehog' },
]);

function attempt(word: string): string {
  try {
    return `${word}: [${center.lookup(word).join(', ')}]`;
  } catch (error) {
    return `${word}: ${(error as Error).message}`;
  }
}

console.log(`opened: builds=${center.builds}`);
const browsed = center.browse();
console.log(`browse: ${browsed.length} sheets, builds=${center.builds}`);
const first = attempt('fox');
console.log(`${first}, builds=${center.builds}`);
exportReady = true; // the export finishes syncing
const second = attempt('fox');
console.log(`${second}, builds=${center.builds}`);
const third = attempt('owl');
console.log(`${third}, builds=${center.builds}`);