How Lodash _.attempt Handles Errors with Try Catch
This article provides an overview of Lodash's _.attempt
utility, detailing how it intercepts script exceptions by abstracting
JavaScript's native try-catch mechanics. By wrapping
function execution in an internal error-handling boundary,
_.attempt converts thrown exceptions into first-class
return values, streamlining control flow and eliminating the need for
verbose, deeply nested native error-handling blocks.
The Mechanics of
_.attempt
In standard JavaScript, unhandled runtime exceptions interrupt
execution and bubble up the call stack unless explicitly captured by a
try-catch statement. Lodash’s _.attempt
utility acts as an execution wrapper designed to intercept these
failures at the point of origin.
Under the hood, _.attempt accepts a function along with
any arguments that need to be forwarded to it. Internally, it invokes
the target function within a native try-catch block:
function attempt(func, ...args) {
try {
return func(...args);
} catch (error) {
return isError(error) ? error : new Error(error);
}
}Instead of allowing an unhandled script error to break the runtime or
trigger global error events (such as window.onerror), the
utility catches the thrown value directly. If the caught object is not
already an instance of an Error, Lodash normalizes it into one before
returning it.
Replacing Nested Control Flow
Traditional native try-catch constructs are statements
rather than expressions, meaning they cannot directly assign values
without declaring mutable variables outside the block:
let data;
try {
data = JSON.parse(rawJson);
} catch (e) {
data = null;
}_.attempt replaces this imperative control flow with a
functional, expression-based pattern. It encapsulates the native
try-catch logic internally, returning either the successful
result of the invoked function or the caught Error object
directly to a single assignment target:
const result = _.attempt(JSON.parse, rawJson);This design isolates parsing and execution failures to the local assignment, preventing unhandled errors from terminating scripts while keeping the codebase linear and readable.
Evaluating Outcomes with
_.isError
Because _.attempt captures errors and returns them as
plain values, standard execution flow does not divert to an external
handler. To process the outcome, developers check the return value using
Lodash's complementary _.isError method:
const result = _.attempt(riskyOperation);
if (_.isError(result)) {
// Handle failure state
console.error("Execution failed:", result.message);
} else {
// Proceed with standard execution
console.log("Success:", result);
}This pattern mirrors error-handling approaches found in languages with explicit error returns, treating exceptions as predictable data rather than control-flow disruptions.
Synchronous Boundaries and Limitations
The isolation provided by _.attempt is strictly
synchronous. The internal try-catch block only traps
exceptions thrown during the immediate, synchronous execution tick of
the passed function.
If the target function returns a Promise or schedules asynchronous
operations (such as setTimeout or fetch),
internal rejections or asynchronous exceptions will bypass the
synchronous try-catch boundary. For asynchronous
operations, native Promise rejection handling (.catch() or
async/await with native try-catch) remains
necessary.