Skip to main content

Command Palette

Search for a command to run...

The Safe Assignment Operator (?=) in JavaScript — No More try-catch

Updated
4 min read
The Safe Assignment Operator (?=) in JavaScript — No More try-catch

The Safe Assignment Operator (?=) in JavaScript — No More try-catch

Error handling in JavaScript has always been verbose. The try-catch pattern works, but it produces deeply nested code, makes control flow harder to follow, and scales poorly when multiple async operations need independent error handling.

The Safe Assignment Operator (?=) is a TC39 proposal that aims to fix this by bringing Go-style error handling to JavaScript.

The Problem with try-catch

Consider a typical async operation:

try {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  console.log(data);
} catch (err) {
  console.error('Something failed:', err);
}

This looks fine for one operation. But real-world code chains multiple async calls, each of which can fail independently:

try {
  const response = await fetch('https://api.example.com/users');
  const users = await response.json();

  try {
    const response2 = await fetch(`https://api.example.com/users/${users[0].id}/posts`);
    const posts = await response2.json();

    try {
      await saveToDatabase(posts);
    } catch (err) {
      console.error('Database error:', err);
    }
  } catch (err) {
    console.error('Posts fetch error:', err);
  }
} catch (err) {
  console.error('Users fetch error:', err);
}

Three levels deep. Each try-catch adds indentation, obscures the happy path, and makes the code harder to maintain. This is sometimes called the "try-catch pyramid of doom."

How ?= Works

The Safe Assignment Operator destructures the result of an expression into a tuple [error, value]:

const [err, response] ?= await fetch('https://api.example.com/data');

if (err) {
  console.error('Fetch failed:', err);
  return;
}

const [parseErr, data] ?= await response.json();

if (parseErr) {
  console.error('Parse failed:', parseErr);
  return;
}

console.log(data);

No nesting. No indentation creep. Each operation handles its own error inline. The control flow reads top to bottom — exactly how the code executes.

Why This Matters

Flat Error Handling

The ?= operator eliminates nesting. Every async call returns a consistent [error, result] tuple, and you handle it immediately. The happy path stays at the top level.

Explicit Error Granularity

With try-catch, a single block catches errors from multiple statements. You often don't know which operation failed without additional logic. With ?=, each operation has its own error variable.

Cleaner Async Pipelines

When chaining multiple operations that can each fail:

const [fetchErr, response] ?= await fetch(url);
if (fetchErr) return handleError(fetchErr);

const [parseErr, data] ?= await response.json();
if (parseErr) return handleError(parseErr);

const [saveErr] ?= await db.save(data);
if (saveErr) return handleError(saveErr);

return { success: true, data };

This pattern scales linearly. Ten operations? Ten flat checks. No pyramid.

Inspiration from Go

If this looks familiar, it's because Go has used this pattern since its inception:

data, err := ioutil.ReadFile("file.txt")
if err != nil {
    log.Fatal(err)
}

Go's approach has been praised for making error handling explicit and impossible to accidentally ignore. The ?= operator brings the same discipline to JavaScript.

The Pattern You Can Use Today

While ?= is still a proposal (not yet part of the ECMAScript standard), you can achieve the same pattern with a simple utility function:

async function safe(promise) {
  try {
    const result = await promise;
    return [null, result];
  } catch (err) {
    return [err, null];
  }
}

// Usage
const [err, response] = await safe(fetch('https://api.example.com/data'));

if (err) {
  console.error(err);
  return;
}

console.log(response);

Five lines of code. No dependencies. Works everywhere today. When ?= eventually ships in browsers, migrating is a one-line search-and-replace.

Current Status

The Safe Assignment Operator is a Stage 1 TC39 proposal as of 2024. This means:

  • It has a formal specification document.
  • It's being actively discussed by the committee.
  • It could change significantly before reaching Stage 3/4.
  • No browser or runtime supports it natively yet.

Track the progress on GitHub.

The Bottom Line

Error handling should be simple, explicit, and flat. The ?= operator doesn't add new capability — you can already handle errors with try-catch. But it changes the ergonomics of error handling in a way that makes code cleaner, more readable, and easier to maintain.

In the meantime, the safe() utility function gives you the same pattern today — zero dependencies, five lines, works everywhere.

Better error handling isn't about catching more errors. It's about making error paths as readable as success paths.


By estebanrfp — Full Stack Developer, dWEB R&D

More from this blog

estebanrfp

13 posts

Full Stack Developer — dWEB R&D. Building distributed systems, P2P databases, and virtual worlds with pure JavaScript.