LimitLoading saved progress…

Limit

Implement limit(fn, n), which wraps a callback so it runs at most n times. The first n invocations call fn and return its result. Every invocation after that returns the result of the last real call, without invoking fn again. Think of it as a "burn after reading" wrapper — there are n real shots, then the wrapper just hands back the last receipt.

Signature

// Returns a wrapped function. The first `n` calls invoke `fn` and return its
// result. Calls n+1 and beyond return the last cached result without calling
// fn. If n is 0 or negative, fn is never invoked and every call returns
// undefined.
function limit<T extends (...args: any[]) => any>(fn: T, n: number): T;

Examples

let calls = 0;
const sayHi = limit((name) => { calls++; return `hi ${name}`; }, 2);

sayHi('Ada');    // 'hi Ada'   (call 1: invokes fn)
sayHi('Bea');    // 'hi Bea'   (call 2: invokes fn — this is the Nth call)
sayHi('Cara');   // 'hi Bea'   (call 3: returns cached, fn NOT invoked)
sayHi('Dee');    // 'hi Bea'   (still cached)
// calls === 2 — fn ran exactly twice
// n = 0 means fn never runs. Every call returns undefined.
const blocked = limit(() => 'should never see this', 0);
blocked(); // undefined
blocked(); // undefined
// Each limit() factory call is independent — its own counter and cache.
const a = limit((x) => x * 2, 1);
const b = limit((x) => x * 10, 1);
a(5);  // 10
b(7);  // 70   (b has its own counter; a's call didn't consume b's quota)
a(99); // 10   (a's cache: the last real result was 10)
b(99); // 70

Notes

  • Nth call still invokes fn. With n = 2, calls 1 and 2 both call fn. Call 3 is the first one that returns the cache. Off-by-one here is the most common bug.
  • Cache is the last invoked result. Post-limit calls return whatever fn returned on call n, regardless of the new arguments. The wrapper ignores its arguments once the limit is reached.
  • Throwing counts as a call. If fn throws on call 2 with n = 3, that throw propagates out of the wrapper and consumes one of the three slots. The counter ticks on entry, not on success.
  • Negative or non-integer n. Treat any n less than 1 (including 0, negatives, NaN) as 0 — fn never runs. Floor non-integers (n = 2.7 behaves like n = 2).
  • Independent instances. Each limit(fn, n) returns a fresh wrapper with its own counter and its own cache. Two wrappers around the same fn do not share state.
  • Forward this and all arguments. For the calls that do invoke fn, pass through every argument and respect this so the wrapper works with .call, .apply, and methods on objects.
Loading editor…