completefrontend
Open lab My progress
THE JAVASCRIPT FIELD GUIDE

Error handling strategies

Decide when JavaScript should fail fast, recover, report uncaught errors, isolate broken UI, log safely, and return result objects instead of surprise exceptions.

By the end, you can
  • 01
    Pick a planDecide when your code should stop at once and when it should keep going with a simpler fallback.
  • 02
    Keep errors smallUse a global handler and an error boundary so one error doesn't break the whole page.
  • 03
    Report safelyLog useful details, hide secrets, and return result objects for failures you expect.

Plan what happens when code fails

The last two lessons, try, catch & finally and Throwing & custom errors, showed the tools. You can catch an error, throw a clearer one, keep the first error as its cause, and clean up. This lesson asks a new question: what should your program do after something goes wrong?

Good programs still have errors. The difference is what they do next. A good program:

  • stops in the right place,
  • keeps the user safe,
  • saves enough details for a developer to fix the bug,
  • and keeps a small problem from growing into a big one.

Sometimes the right move is to stop at once. Sometimes it is to show a simpler version of the page. Sometimes a function should hand back an object that says "this failed", without throwing at all.

The table below lists five plans. Read each row from left to right: the plan, when to pick it, and what your code does.

Five ways to handle an error
PlanWhen do you pick it?What does your code do?
Fail fastGoing on with bad data would break things or hide a bug.Throw an error, stop the current task, show a clear message, and fix the cause.
RecoverThe user can go on safely with a fallback: a default value, a saved copy, or one feature turned off.Catch the error where it happens, show the simpler version, and log what happened.
Global handlerNo code caught the error.Log it and show a general message. Don't use it as your normal way to handle errors.
Error boundaryOne part of the page broke, but the rest still works.Catch the error around that one part, and show a fallback just for that part.
Result objectThe failure is expected, and the caller should choose what to do.Return { ok: true, value } or { ok: false, error } instead of throwing.
The main idea

An error handling strategy is a plan you choose ahead of time. It answers four questions:

  • Where is the error caught?
  • What does the user see?
  • What do developers get to read later?
  • Does the program stop, or keep going with fewer features?

A promise is an object for a result that arrives later. This lesson uses promises only once, in a short paragraph about the unhandledrejection event. The Promises lessons in Stage 6 teach them in full.

Stop at once, or keep going?

INTERACTIVE

Fail fast means: stop as soon as your code sees data it can't trust. Throw an error and end the current task. Here are three examples where guessing would be dangerous:

  • a checkout total that is not a number,
  • a missing security token on a form,
  • a settings file that makes no sense.

Stopping early keeps the bug small and easy to see. If the code kept going, the bad data could spread to other places first.

Real-life analogyFail fast is a smoke detector

A smoke detector is loud on purpose. It doesn't wait and hope the smoke goes away. It sounds the alarm so people can act while the fire is still small.

In real life: Smoke appears
In JavaScript: Bad data appears
In real life: The alarm sounds right away
In JavaScript: The code throws right away
In real life: People act while the fire is small
In JavaScript: Developers fix the bug before bad data spreads

Recover means: keep going, but with less. The page stays honest about what it can do. Some examples:

  • A missing profile photo becomes the first letter of the name.
  • A saved color theme that can't be read becomes the default theme.
  • A broken "recommended for you" box disappears, and the article is still readable.

The simpler thing you show instead is called a fallback.

Show the first letter when there is no photoPop out in the code editor (opens in a new tab)JavaScript
const user = { name: "Asha", avatarUrl: "" };
const avatar = user.avatarUrl || user.name[0];
console.log(avatar);
  1. Line 1 makes a user whose avatarUrl (the photo address) is an empty string.
  2. Line 2 uses || (or). An empty string counts as false, so || picks the right side: user.name[0], the first letter, "A".
  3. Line 3 prints A.
Real-life analogyRecovering is a spare tire

A spare tire is not as good as a normal tire. But it lets you keep driving, slowly, until you can fix the real one. You still have to ask: is this spare safe enough for this road?

In real life: A flat tire
In JavaScript: A feature or an input fails
In real life: A spare tire
In JavaScript: A fallback value, saved copy, or simpler page
In real life: Drive slowly to a repair shop
In JavaScript: Keep the user going and record the problem

The next demo reads a config: a small piece of JSON text with an app's settings. This config has no endpoint (the web address the app sends data to). The same function can handle that in two modes:

  • Strict mode fails fast: it throws an error.
  • Lenient mode recovers: it uses default values and adds a warning.

Both are fine plans. Which one is right depends on the app.

The same config with no endpoint, in two modes. Left, strict: the code runs throw error and stops with Error: endpoint is required. Right, lenient: the config cart holds the default endpoint /api, and the code keeps going with a warning.strictfail fastthrow errorError: endpoint is requiredlenientrecoverendpoint: "/api"configuses defaults + a warning
Strict mode throws and stops. Lenient mode uses the default endpoint, "/api", and keeps a warning.
Stop or keep going when the config has no endpoint
Step 0 of 5Ready
Your turn: follow the blue line

Step through the same config with no endpoint, in strict or lenient mode. Strict stops with an error. Lenient goes on with the defaults and a warning.

Running in
  1. script
Next: line 1
 function parseConfig(text, mode) {  try {    const draft = JSON.parse(text);    if (typeof draft.endpoint !== "string") throw new Error("endpoint is required");    if (!Number.isInteger(draft.retries) || draft.retries < 0) throw new Error("retries must be a non-negative integer");    return { ok: true, config: { ...defaults, ...draft }, warnings: [] };  } catch (error) {    if (mode === "strict") throw error;    return { ok: true, config: defaults, warnings: [error.message] };  }} parseConfig("{ \"retries\": 2, \"cache\": true }", "strict");
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
Pick the mode. Strict uses line 10; lenient uses line 11.

When you change the mode, the step-through starts again with the same config that has no endpoint.

A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Try different config text
The parseConfig functionPop out in the code editor (opens in a new tab)JavaScript
const defaults = { endpoint: "/api", retries: 3, cache: false }; function parseConfig(text, mode) {  try {    const draft = JSON.parse(text);    if (typeof draft.endpoint !== "string") throw new Error("endpoint is required");    if (!Number.isInteger(draft.retries) || draft.retries < 0) throw new Error("retries must be a non-negative integer");    return { ok: true, config: { ...defaults, ...draft }, warnings: [] };  } catch (error) {    if (mode === "strict") throw error;    return { ok: true, config: defaults, warnings: [error.message] };  }}
What parseConfig gives backstrict
{ "retries": 2, "cache": true }

It stopped with an error

{
  "ok": false,
  "error": "endpoint is required"
}
Try it yourself
Config text:

Stopped: endpoint is required

Pick a config text and a mode. Strict mode stops when the config is bad. Lenient mode uses the defaults and adds a warning.
Fail fast or recover?
  • A checkout total is NaN just before the card is charged
  • A profile photo loads slowly, but the first letter of the name is available
  • A security token is missing from a form when it is sent
  • The saved color theme can't be read
  • Before changing a database, a check finds a version number that should be impossible
  • A "recommended for you" box can't load on an article page
Try it yourself
0 of 6 correct

For each case, pick the safer plan.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Global handlers: the last place to catch an error

SANDBOXED

The best place to catch an error is close to where it happens. There, your code knows enough to fix the problem or explain it to the user.

Sometimes no code catches an error. The browser gives you one last place to hear about it: a global handler. It is a function that runs for any error that nobody caught.

You set one up with window.addEventListener("error", handler). That line means "when an error event happens on the page, call handler". An event is a message the browser sends when something happens. The handler gets an object with details, such as:

  • message: the error text,
  • filename: the file the error came from,
  • lineno: the line number.

Two things to remember:

  • window exists only in the browser. A Node script doesn't have it.
  • A global handler only reports the error. It does not make the failed task work. Keep catching errors close to where they happen.

The browser has a second event, unhandledrejection. A promise rejects when its work fails. If no code handles that failure, the browser sends this event. Here is the smallest example: Promise.reject(new Error("No handler"));. It makes a promise that has already failed, and nothing handles it. You don't need to understand promises yet. Just remember: this event is the promise version of the error event.

Real-life analogyGlobal handlers are the central alarm panel

A big building has an alarm in each room and one central panel at the front desk. If nobody answers a room alarm, the panel records it so staff can respond.

In real life: A room alarm nobody answered
In JavaScript: An error that no code caught
In real life: The panel records which room and which alarm
In JavaScript: The handler records the message, file, line, or reason
In real life: Each room still needs its own fire doors
In JavaScript: Your code still needs its own catch blocks

The lab below runs inside an iframe: a small page inside this page. The errors it makes stay inside that small page, so they can't break the lesson.

Global handlers in a safe small page
The two listeners inside the iframePop out in the code editor (opens in a new tab)JavaScript
window.addEventListener("error", (event) => {  parent.postMessage({    kind: "error",    message: event.message,    filename: event.filename,    lineno: event.lineno,  }, "*");}); window.addEventListener("unhandledrejection", (event) => {  parent.postMessage({    kind: "unhandledrejection",    reason: String(event.reason && event.reason.message || event.reason),  }, "*");});
The small page (iframe)scripts only
Events received

No events yet.

Try it yourself

The two listeners live only inside the small page (the iframe). Press a button. The iframe sends the event details back here.

The lesson page accepts a message only if it came from this iframe (it checks event.source). The iframe may only run scripts, so its errors can't break the lesson page.

Error boundaries: one broken part, not a broken page

STEP THROUGH

A page is often built from small parts called widgets, like a sales box or an alerts box. To render a widget means to build what it shows on the screen.

An error boundary is a try/catch wrapped around one widget's render. If that widget throws, the boundary shows a simple fallback in its place. The rest of the page still shows up.

Real-life analogyAn error boundary is a fire door

A fire door doesn't put out the fire. It keeps the fire in one room, so the rest of the house stays safe.

In real life: One room catches fire
In JavaScript: One widget throws while it renders
In real life: The fire door closes
In JavaScript: The boundary catches that widget’s error
In real life: The rest of the house is safe
In JavaScript: The other widgets still render

React, a popular tool for building pages, has its own error boundary feature. This course hasn't taught React yet, so the demo uses plain JavaScript. It has three widgets: Sales, Alerts, and Uptime. The Alerts widget throws. Each widget gets its own boundary.

In React, an error boundary only catches errors that happen while the page is being built. It does not catch errors in:

  • click handlers and other event handlers,
  • timers, such as setTimeout,
  • code that runs later, after the page is built.

Catch those errors in their own code.

A dashboard window with three cards in order. Sales: ₹12,400 renders. Alerts throws, so the boundary catches it and shows Alerts: temporarily unavailable. Uptime: 99.98% still renders.dashboardSales: ₹12,400Alerts: temporarily unavailableUptime: 99.98%rendersthrew, caughtstill renders
The Alerts widget throws, so its catch block returns a fallback card. Sales and Uptime still render.
Step through a boundary around each widget
Step 0 of 5Ready
Your turn: follow the blue line

Step through a plain JavaScript boundary around three widgets. One throws, but the other two still render.

Running in
  1. script
Next: line 13
function renderWidget(widget) {  return "<article>" + widget.title + ": " + widget.render() + "</article>";} function renderWithBoundary(widget) {  try {    return renderWidget(widget);  } catch (error) {    return "<article>" + widget.title + ": temporarily unavailable</article>";  }} 
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Turn the boundary on and off
The boundary codePop out in the code editor (opens in a new tab)JavaScript
function renderWidget(widget) {  return "<article>" + widget.title + ": " + widget.render() + "</article>";} function renderWithBoundary(widget) {  try {    return renderWidget(widget);  } catch (error) {    return "<article>" + widget.title + ": temporarily unavailable</article>";  }} widgets.map(renderWithBoundary);
Dashboardboundary on
Sales

₹12,400

Alerts

Temporarily unavailable

Alerts service offline
Uptime

99.98%

Try it yourself

The Alerts widget throws. The boundary catches the error and returns one fallback card. Sales and Uptime still render.

A plain JavaScript error boundary: a try/catch around the render of each widget.

Logging & monitoring: save the details safely

PRACTICAL

A log is a saved record of what happened in your app. If you catch an error but nobody can tell what went wrong, the job is only half done. A good error log answers these questions:

  • What failed? (the message)
  • Where in the code? (the stack, the list of function calls that led to the error)
  • On which page, and after which click or action?
  • In which release, meaning which version of the app?

Logs must also be safe. Never put these in a log:

  • passwords,
  • tokens (secret codes that prove who a user is),
  • other secrets, card numbers, or private details about a person.

To redact a value means to replace it with a marker like [redacted] before saving it.

Some errors happen thousands of times. You can sample them: save only some copies, enough to understand the problem.

A monitoring service, such as Sentry, collects errors from many users. It groups the same error together, alerts your team, and shows which release each error came from. Your code still decides what is safe to send.

The user needs something different from the log: a short, clear next step. For a failed save, show We couldn't save your changes. Try again. Keep the message, stack, page, and release in the log. Never leave a catch block empty. If you can't recover, log the error or throw it again, so the problem doesn't disappear.

Hide secrets, then build the logPop out in the code editor (opens in a new tab)JavaScript
function redactSecrets(context) {
  const clone = { ...context };
  for (const key of Object.keys(clone)) {
    if (/token|password|secret/i.test(key)) clone[key] = "[redacted]";
  }
  return clone;
}

function logError(error, context) {
  return {
    message: error.message,
    stack: error.stack,
    context: redactSecrets(context),
    release: "2026.09.25",
  };
}

function userMessageFor(error) {
  return error.message ? "We couldn't save your changes. Try again." : "Something went wrong. Try again.";
}

const log = logError(new Error("database detail"), { authToken: "secret", route: "/settings" });
console.log(log.context.authToken, userMessageFor(new Error("database detail")));
  1. Lines 1–7, redactSecrets: line 2 copies the context with { ...context }, so the original object stays the same.
  2. Line 3 loops over every key in the copy. Line 4 checks each key name with /token|password|secret/i. It matches any key that contains "token", "password", or "secret". The i means capital letters count too, so authToken matches. A match is replaced with "[redacted]".
  3. Lines 9–16, logError: it builds the log with the message, the stack, the safe context, and the release.
  4. Lines 18–20, userMessageFor: it picks the short text the user sees.
  5. Line 23 prints [redacted] We couldn't save your changes. Try again.
Left, the context passed in: authToken is secret, route is /settings. An arrow labelled redactSecrets points right. Right, log.context: authToken is [redacted], route is still /settings.contextauthToken: "secret"route: "/settings"redactSecretslog.contextauthToken: "[redacted]"route: "/settings"token hidden, route kept
redactSecrets hides the token before the log keeps it. The route stays, because it helps you find the bug.
A monitoring service can't choose for you

A monitoring service can group the same error and alert your team. But it doesn't know your privacy rules. It also can't tell if a fallback is honest. Your code makes those choices.

Result objects: return the failure instead of throwing it

STEP THROUGH

Throwing is a good fit for surprises and bugs. But some failures are normal and expected:

  • Text a user typed may not be valid JSON.
  • A search may find nothing.
  • A save may fail twice before it works.

For these, a function can return a result object instead of throwing. It always has an ok property:

  • { ok: true, value } means it worked. value is the answer.
  • { ok: false, error } means it failed. error says why.

The code that called the function checks ok first, then uses value or error.

Real-life analogyA result object is a sealed envelope

Instead of throwing an error at you, the function hands you a sealed envelope. The label on the outside says what is inside: an answer, or an error.

In real life: The label on the envelope says success
In JavaScript: { ok: true, value }
In real life: The label on the envelope says failure
In JavaScript: { ok: false, error }
In real life: You read the label before you open it
In JavaScript: The caller checks result.ok before it uses result.value
Two calls to tryParse. Left, tryParse with valid JSON returns a result cart holding ok: true and the value {name:"Ada"}, so the code prints Ada. Right, tryParse("not json") returns a result cart holding ok: false and error: SyntaxError, so the if block is skipped.tryParse('{ "name": "Ada" }')ok: truevalue: {name:"Ada"}resultprints AdatryParse("not json")ok: falseerror: SyntaxErrorresultif block skipped
Neither call throws. Each one returns a result object, and ok says which kind it is.
Step through a result object
Step 0 of 4Ready
Your turn: follow the blue line

Follow a result object from reading JSON to the safe success path.

Running in
  1. script
Next: line 9
function tryParse(text) {  try {    return { ok: true, value: JSON.parse(text) };  } catch (error) {    return { ok: false, error };  }} if (result.ok) {  console.log(result.value.name);}
CallStoreChangeResultBlue line: runs next. Ran: already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.
Try valid and invalid JSON
The tryParse functionPop out in the code editor (opens in a new tab)JavaScript
function tryParse(text) {  try {    return { ok: true, value: JSON.parse(text) };  } catch (error) {    return { ok: false, error };  }} const result = tryParse('{ "name": "Ada" }');if (result.ok) {  console.log(result.value.name);}
Result objectworked
{
  "ok": true,
  "value": {
    "name": "Ada"
  }
}
Try it yourself

The result says ok: true, so the caller can read value.

This panel works like tryParse: when the JSON is bad, you get a result object, not a thrown error.

Result objects also work well with small helper functions. A retry helper calls a task again when it fails. It stops at the first result with ok: true. No throwing is needed to end the loop.

Only retry temporary failures, such as a network that is down for a moment. Don't retry a failure that will happen every time, such as a wrong password. The code below marks a temporary error with transient: true ("transient" means short-lived).

A good retry also waits longer before each new try: 100 ms, then 200 ms, then 400 ms. This is called exponential backoff. Always stop after a small number of tries.

Retry a task that returns result objectsPop out in the code editor (opens in a new tab)JavaScript
function retry(operation, attempts = 3) {
  let last;
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    const result = operation();
    if (result.ok) return result;
    last = result;
    if (!result.error.transient) return result;
    if (attempt < attempts) {
      const delayMs = 100 * 2 ** (attempt - 1);
      console.log("retry after", delayMs, "ms");
    }
  }
  return last;
}

let tries = 0;
function operation() {
  tries += 1;
  return tries < 3
    ? { ok: false, error: Object.assign(new Error("offline"), { transient: true }) }
    : { ok: true, value: "saved" };
}

const result = retry(operation);
console.log(result.value, tries);
  1. Line 4 runs the task once and keeps its result object.
  2. Line 5: if ok is true, return the result. We're done.
  3. Line 6 remembers the last failure, in case every try fails.
  4. Line 7: if the error is not temporary, stop now. Trying again won't help.
  5. Lines 8–11: if there are tries left, work out the wait. 2 ** (attempt - 1) means 2 to the power of attempt - 1, so the waits are 100, then 200. This demo only prints the wait; it doesn't really pause.
  6. Lines 16–22, operation: it fails twice with a temporary "offline" error, then returns { ok: true, value: "saved" }. Object.assign adds transient: true to the error.
  7. Line 24 starts the retry. Line 10 prints retry after 100 ms, then retry after 200 ms. The third try works, so line 25 prints saved 3.

Where you’ll use this

You'll pick an error plan wherever your code works with users, data, or other servers. The rule: catch the error at the closest place that can make a good choice. Errors you didn't expect can go up to a boundary or a global handler, which reports them.

Read each row as: in this part of an app, this plan usually fits, and here is what it looks like.

Common places to pick a plan
Where in the app?Which plan usually fits?What does it look like?
A formRecover where the error happensShow a message next to each wrong field. Don't send the bad data.
Working out a paymentFail fastStop if the total is NaN or the currency is missing.
A dashboard widgetError boundaryShow one fallback card. The other widgets stay on screen.
JSON a user typedResult objectReturn { ok: false, error } and let the caller pick a message.
An error nobody caught in the browserGlobal handlerSave the message, stack, action, and release, with secrets hidden.

To practice reading error messages and the stack, see the Reading errors and Debugging lessons. A closure can help you build a logger that remembers the release; see the Closures lesson.

Common misconceptions

  • “If I catch it, it’s fixed.” Not quite. A catch block must do one of three things: recover honestly, add details and throw again, or log the error safely.
  • “A global handler means I don’t need my own catch blocks.” Not quite. A global handler runs last. By then it is usually too late to recover in a specific way.
  • “Always recover if you can.” Not quite. Recovering with unsafe data is worse than stopping with a clear error.
  • “Error boundaries are only a React thing.” Not quite. React has a feature with that name, but the idea works anywhere: keep one broken part of the page from breaking the rest.
  • “Log everything.” Not quite. Log enough to find the bug. Hide secrets, and save only some copies of an error that repeats a lot.
  • “Result objects are always better than throwing.” Not quite. Use them for failures you expect. Throw for bugs and for data that should never happen.

Practice exercises

5 EXERCISES
Exercise 1 · Warm-upRead a function that returns a result object

Read the code without running it. The ? : on the last line picks result.value when result.ok is true, and "bad" when it is false. What does it print?

Starter codePop out in the code editor (opens in a new tab)JavaScript
function parseCount(text) {
  try {
    return { ok: true, value: JSON.parse(text).count };
  } catch (error) {
    return { ok: false, error };
  }
}
const result = parseCount('{ "count": 3 }');
console.log(result.ok ? result.value : "bad");

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upPick the plan

    A checkout is about to charge a card, but the total is NaN ("not a number"). Should it fail fast or recover?

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const total = Number.NaN;
    console.log(Number.isNaN(total) ? "fail fast" : "recover");

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeGive each widget its own boundary

      Finish the code so one widget that throws can't stop the other widgets.

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      const widgets = [salesWidget, alertsWidget, uptimeWidget];
      const cards = widgets.map((widget) => {
        // Wrap one widget render here.
      });
        Exercise 4 · PracticeHide the token before logging

        What does this code print for authToken?

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        function redactSecrets(context) {
          const clone = { ...context };
          for (const key of Object.keys(clone)) {
            if (/token|password|secret/i.test(key)) clone[key] = "[redacted]";
          }
          return clone;
        }
        console.log(redactSecrets({ userId: 7, authToken: "abc", password: "p" }).authToken);

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeName the event for an unhandled promise failure

          A promise fails, and no code handles the failure. Which browser event fires?

          Answer, then press Check. Spacing and letter case don’t matter.

            Check your understanding

            7 QUESTIONS
            Error handling strategies quiz · 7 questionsScore: first tries count
            1. Question 1 of 7When should code fail fast?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does the tryParse code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function tryParse(text) {
                try { return { ok: true, value: JSON.parse(text) }; }
                catch (error) { return { ok: false, error }; }
              }
              const result = tryParse("not json");
              console.log(result.ok ? "value" : "failure");

              Choose an answer to see the explanation.

            3. Question 3 of 7What is a browser error or unhandledrejection listener for?

              Choose an answer to see the explanation.

            4. Question 4 of 7In this lesson's plain JavaScript boundary, what happens when one widget throws?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does the redact code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              function redact(context) {
                const copy = { ...context };
                if ("apiToken" in copy) copy.apiToken = "[redacted]";
                return copy;
              }
              console.log(redact({ apiToken: "secret", action: "save" }).apiToken);

              Choose an answer to see the explanation.

            6. Question 6 of 7Why use a result object?

              Choose an answer to see the explanation.

            7. Question 7 of 7What does the retry loop code print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              let tries = 0;
              function operation() {
                tries += 1;
                return tries < 2 ? { ok: false, error: new Error("wait") } : { ok: true, value: "done" };
              }
              let result;
              for (let i = 0; i < 3; i += 1) {
                result = operation();
                if (result.ok) break;
              }
              console.log(result.value);

              Choose an answer to see the explanation.

            Key takeaways

            • Fail fast when going on would spread bad data, hide a bug, or be unsafe.
            • Recover when there is an honest fallback, such as a default or a simpler page.
            • Global error and unhandledrejection handlers report errors nobody caught. They don't replace your own catch blocks.
            • An error boundary keeps one broken part of the page from breaking the rest.
            • A good log has the message, stack, page, user action, and release. Hide secrets first.
            • A result object says plainly whether a call worked: { ok: true, value } or { ok: false, error }.

            An error handling strategy means: catch each error at the closest place that can make a safe choice, and report everything else clearly.

            Up next: Synchronous vs asynchronous.

            CompleteFrontend Clear concepts. Working examples.