fix(util): cache nullish values in lazy() (#11564)

lazy() used defaultValue ??= cb(), so a callback returning null or undefined
was never cached and re-ran on every call, breaking the compute-once contract
(any side effect in the callback repeated). Track a called flag instead of
relying on value nullishness.
This commit is contained in:
spokodev
2026-08-27 14:01:43 +01:00
committed by GitHub
parent 99af6d8bec
commit a258a488b9
2 changed files with 31 additions and 2 deletions
+24
View File
@@ -29,4 +29,28 @@ describe('lazy', () => {
expect(callback).toHaveBeenCalledOnce();
expect(cachedValue).toEqual('Lorem Ipsum');
});
test('GIVEN undefined callback with cached value THEN returns the same', () => {
const callback = vi.fn<() => string | undefined>(() => undefined);
const lazyStoredValue = lazy(callback);
lazyStoredValue();
const cachedValue = lazyStoredValue();
expect(callback).toHaveBeenCalledOnce();
expect(cachedValue).toBeUndefined();
});
test('GIVEN null callback with cached value THEN returns the same', () => {
const callback = vi.fn(() => null);
const lazyStoredValue = lazy(callback);
lazyStoredValue();
const cachedValue = lazyStoredValue();
expect(callback).toHaveBeenCalledOnce();
expect(cachedValue).toBeNull();
});
});
+7 -2
View File
@@ -13,6 +13,11 @@
// eslint-disable-next-line promise/prefer-await-to-callbacks
export function lazy<Value>(cb: () => Value): () => Value {
let defaultValue: Value;
// eslint-disable-next-line promise/prefer-await-to-callbacks
return () => (defaultValue ??= cb());
let called = false;
return () => {
if (called) return defaultValue;
called = true;
// eslint-disable-next-line promise/prefer-await-to-callbacks
return (defaultValue = cb());
};
}