Recent chats
ASKGEEZ Q&A GUIDETechnology & devices

How can I preserve the original error when adding context to an async failure?

Quick answer

When you catch a failure, wrap it in a new Error that describes the operation and pass the caught value as its cause. For example, use throw new Error("Loading records failed", { cause: err }). The higher-level caller can read the context from the wrapper and inspect cause for the underlying failure; distinguish errors by their causes or types, not by parsing message text. [S1]

LET’S WORK THROUGH IT

The question, the details, the next step.

Question

I have an async function that fails while doing one step of a larger job. I want the error to say which operation failed, but I don't want to lose the original error. Should I just throw a new error with a more helpful message?

Geez

You can add a new, more specific error, but keep the caught value as its cause rather than replacing it. The wrapper gives a higher-level handler useful context, while the cause retains the underlying failure for inspection. In JavaScript, the Error constructor supports a cause option; Microsoft’s reference gives the pattern new Error('Loading records failed', { cause: err }). [S1]

For example, assuming readRecords() is an existing async operation:

async function loadRecords() {
  try {
    return await readRecords();
  } catch (err) {
    throw new Error("Loading records failed", { cause: err });
  }
}

This adds the context “Loading records failed” without pretending that message is the original failure. Choose wording that names the operation or stage, not a cause you have not established.

Follow-up question

What does the propagation path look like? If another function catches this, how can it see both the helpful message and the original problem?

Geez

Let the wrapper continue to the layer that can decide what to do with it. That layer can inspect the wrapper’s message for operation context and its cause for the underlying value. For example:

async function refreshPage() {
  try {
    const records = await loadRecords();
    return showRecords(records);
  } catch (err) {
    console.error("Page refresh could not load records:", err);
    console.error("Underlying failure:", err.cause);
    throw err;
  }
}

Here, loadRecords() adds context, and refreshPage() receives that contextual error. This example logs both values and rethrows the same wrapper, so a still-higher caller can handle it too. The cited reference supports inspecting a wrapper’s cause; it does not prescribe where an application should log, display, or handle errors. [S1]

Follow-up question

How should I word the wrapper, and how do I avoid making error handling brittle if the message changes?

Geez

Make the wrapper message answer “which operation failed?” For instance, “Saving draft failed” is more useful than “Something went wrong.” Keep it factual: if you only know that saving failed, do not label the cause as a network problem unless you have evidence for that.

For handling, avoid treating message text as a stable identifier. The JavaScript reference advises using cause to distinguish errors rather than parsing messages. [S1] If your application needs to branch on a particular underlying failure, inspect a suitable property or type that your code actually defines or receives; the supplied reference does not establish what properties every possible cause will have. Also, wrapping is only useful if the value you caught is the failure you intend to preserve: pass that value through as cause instead of constructing an unrelated replacement.

Follow-up question

Can you give me a small reusable pattern I can adapt, including a clear propagation path and a check before I use it?

Geez

Assuming the operation is saveDraft(), adapt the operation names and messages to your code:

async function saveDraftWithContext() {
  try {
    return await saveDraft();
  } catch (err) {
    throw new Error("Saving draft failed", { cause: err });
  }
}

async function publishDraft() {
  try {
    return await saveDraftWithContext();
  } catch (err) {
    console.error("Publishing could not save the draft:", err);
    console.error("Original cause:", err.cause);
    throw err;
  }
}

The path is: saveDraft() fails, saveDraftWithContext() adds a specific wrapper while retaining the caught value as cause, and publishDraft() receives that wrapper. [S1] Before adopting the pattern, check that the wrapper describes the stage that failed, the caught value is passed as the cause, and higher-level handling does not accidentally discard the wrapper or cause. The source explains the JavaScript cause pattern, but does not establish compatibility for every runtime or build target; check the environment your code must support. [S1]

A practical checklist

  • Name the operation or stage that failed in the wrapper message.

  • Pass the caught value as { cause: err } when constructing the new Error.

  • Let the contextual error reach the layer that needs to inspect or handle it.

  • Use causes or suitable error types for distinctions; do not parse message text.

  • Check runtime and build-target support for the cause option.

A few more useful answers

Should I replace the caught error’s message with a more descriptive one?

Add a contextual wrapper and retain the caught value as its cause. That keeps the operation context and the underlying failure available separately. [S1]

Can I rely on the exact wording of an error message to identify its cause?

Avoid that approach. The reference recommends using cause to distinguish errors rather than parsing messages. [S1]

Does this pattern work in every JavaScript runtime or build target?

The cited reference notes that availability and transpilation details may vary. Check the environment your code targets; the source does not establish universal compatibility. [S1]

Sources

References used for this guide.

  1. [S1] JavaScript Error object: preserving an underlying cause
    learn.microsoft.com · Checked

YOUR QUESTION. YOUR DETAILS.

Let’s work through your situation.

Get a tailored answer or help with your next task. Starts at $1, with a quote before you pay.

Ask your own question
← Back to Geez at Work