Course contentsShow
← Node.js
Lesson 211 of 1,9803. Asynchronous Patterns and Flow ControlFree lesson

Error Propagation in Async Pipelines

You will structure your code so that errors bubble naturally through layered async/await abstractions.

In this lesson, you will learn how to design your async pipelines so that errors flow cleanly from deep internal functions up to your main application handler.

When building complex data pipelines—such as fetching, transforming, and saving data—it is tempting to wrap every small step in a try/catch block. However, if you "swallow" an error by logging it inside a helper function without re-throwing it, your pipeline will continue to execute as if nothing went wrong.

The secret to clean error propagation is to let the error bubble up. Instead of catching errors at every layer, allow them to traverse your async calls naturally until they reach the final "entry point" of your pipeline.

The Pipeline Flow

Imagine a pipeline where fetchData calls processData, which then calls saveData. If saveData fails, the error should naturally "pop" back through the stack.

async function saveData(data) {
  // If this fails, the error is automatically passed to the caller
  throw new Error("Database connection lost"); 
}

async function processData(rawData) {
  // We don't catch here, so the error passes through naturally
  return await saveData(rawData.transform());
}

async function runPipeline() {
  try {
    await processData({ some: "data" });
  } catch (err) {
    // We handle the error once, at the highest level of abstraction
    console.error("Pipeline failed:", err.message);
  }
}

By keeping your inner functions "error-agnostic," you write less boilerplate code and ensure that your main logic remains the single source of truth for error handling. When you write a function, ask yourself: "Does this function know how to fix this specific error?" If the answer is no, do not catch it—let it propagate. This strategy ensures that your async pipeline is predictable and that errors are always caught by the logic capable of resolving them.

Key Takeaway: Avoid swallowing errors in intermediate functions; allow them to bubble up to the entry point so you can handle your application flow from a single, centralized location.