Lodash _.once Cached Value When an Error Is Thrown
In the Lodash JavaScript library, wrapping a function with
_.once guarantees that the target function executes only on
its first invocation. If that initial execution inherently throws a
fatal error, the exception bubbles up immediately to the caller,
preventing any value from being assigned to the internal cache. On all
subsequent invocations, _.once does not re-execute the
function and instead returns undefined as its cached
value.
Internal Mechanism of
_.once
Under the hood, Lodash implements _.once(func) by
delegating directly to _.before(2, func). The internal
before wrapper tracks invocation state through a counter
initialized to 2 and an unassigned local variable,
result.
When the wrapped function is called for the first time:
- The counter is decremented from
2to1. - Because the counter remains greater than
0, the wrapper attempts to evaluateresult = func.apply(this, arguments). - If
functhrows an unhandled exception, JavaScript immediately halts execution inside the wrapper and propagates the error up the call stack. - Because the throw interrupts execution before the assignment
expression can resolve,
resultremainsundefined.
Subsequent Invocations
When the wrapped function is called a second time (or any subsequent time):
- The counter is decremented again, moving from
1to0. - The conditional check determining whether to execute the function
evaluates to
falsebecause the counter is no longer greater than0. - The underlying function reference is cleared to allow garbage collection.
- The wrapper immediately returns the contents of
result.
Because result was never assigned a value during the
failed initial run, the returned cached value is
undefined.
Practical Implications
Lodash treats the execution attempt itself as the "one" run,
regardless of whether it succeeded or failed with an exception. It does
not provide built-in retry mechanics for failed executions. If a wrapped
operation fails catastrophically on its first attempt, subsequent
callers will silently receive undefined without
re-triggering the logic or re-throwing the original error. Developers
requiring fault tolerance or cached rejections should instead handle
exceptions inside the function itself or rely on Promise-based
memoization patterns.