Course contentsShow
← Node.js
Lesson 1425 of 1,98013. Error Handling and Resilient SystemsFree lesson

The Pattern of Error Result Objects

You will evaluate the trade-offs of returning an error result object instead of throwing, particularly for deep business logic.

In this lesson, you will learn how to shift from throwing exceptions to returning explicit "result objects" to make your deep business logic more predictable and easier to test.

The Problem with Throwing

In Node.js, throw is an "out-of-band" signal. It breaks the normal flow of execution, jumping out of your current function stack to the nearest catch block. While convenient, deep business logic often suffers because throw creates "hidden" control flow. You cannot tell if a function might throw just by looking at its return type, leading to fragile code that crashes if you forget a try-catch wrapper.

The Error Result Pattern

Instead of throwing, you return a structured object that describes the outcome of an operation. This forces the caller to explicitly acknowledge the failure. A common approach is the "Discriminated Union" pattern:

function processPayment(amount) {
  if (amount <= 0) {
    return { ok: false, error: 'INVALID_AMOUNT', code: 400 };
  }
  // Logic success
  return { ok: true, data: { transactionId: 'abc-123' } };
}

const result = processPayment(-50);

if (!result.ok) {
  console.error(`Operation failed: ${result.error}`);
  return;
}

console.log(`Success: ${result.data.transactionId}`);

Trade-offs: Why Choose This?

  1. Predictability: The function signature acts as a contract. The developer knows exactly what shape the response takes, whether success or failure.
  2. Reduced Boilerplate: You avoid the "pyramid of try-catch" nesting that often occurs when deep logic throws exceptions.
  3. Better Testing: You can easily assert against the returned error object without needing assert.throws() or complex promise rejection handling in your test suites.

However, the trade-off is verboseness. You must check result.ok manually at every step, which can feel repetitive. Furthermore, if you do use this pattern, you must be disciplined: if you return a result object but occasionally still throw, you will break the consumer's logic, leading to the very instability you were trying to fix. Use this pattern for your core business services, but reserve throw for truly exceptional, unrecoverable system failures like configuration errors or fatal database connection loss.

Key Takeaway: Returning error result objects transforms failure from an invisible "jump" into a first-class data type, making your business logic explicit, testable, and resilient.