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:

  1. The counter is decremented from 2 to 1.
  2. Because the counter remains greater than 0, the wrapper attempts to evaluate result = func.apply(this, arguments).
  3. If func throws an unhandled exception, JavaScript immediately halts execution inside the wrapper and propagates the error up the call stack.
  4. Because the throw interrupts execution before the assignment expression can resolve, result remains undefined.

Subsequent Invocations

When the wrapped function is called a second time (or any subsequent time):

  1. The counter is decremented again, moving from 1 to 0.
  2. The conditional check determining whether to execute the function evaluates to false because the counter is no longer greater than 0.
  3. The underlying function reference is cleared to allow garbage collection.
  4. 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.