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


