JavaScript is constantly evolving, and every once in a while, it delivers a feature that revolutionizes how we write code. The new safe assignment operator (?=) is one such feature. It promises to simplify error handling and make our code cleaner and more intuitive.
No More try-catch Boilerplate
Let’s face it: try-catch blocks can quickly clutter your code, especially when you’re working with asynchronous functions. Consider the traditional way of fetching data with try-catch:
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data");
if (!response.ok) {
throw new Error("Network response was not ok");
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Error fetching data:", error);
}
}
Notice the nesting? We not only check for a failed fetch but also handle potential issues with response.json(). This often leads to verbose and deeply nested code.
Enter the Safe Assignment Operator (?=)
With the safe assignment operator, you can handle errors more cleanly and intuitively. Here’s how the same code looks with ?=:
const [err, res] ?= await fetch("https://api.example.com/data");
if (err) {
console.error("Error fetching data:", err);
} else {
console.log("Fetched data:", res);
}
Cleaner and More Readable Code
The ?= operator helps us:
- Eliminate deep nesting: No need for multiple
tryblocks. - Write intuitive error handling: Errors can be handled alongside the result, keeping the flow of logic linear.
Using ?= with Node.js’s fs Module
Let’s look at a practical example with the fs module, which often requires meticulous error handling. Traditionally, we might use fs.readFile like this:
Old Way with try-catch
const fs = require("fs").promises
async function readFileContent(filePath) {
try {
const data = await fs.readFile(filePath, "utf-8");
console.log("File content:", data);
} catch (error) {
console.error("Error reading file:", error.message);
}
};
Now, using ?=:
New Way with ?=
const fs = require(“fs”).promises;
async function readFileContent(filePath) {
const [err, data] ?= await fs.readFile(filePath, "utf-8");
if (err) {
console.error("Error reading file:", err.message);
} else {
console.log("File content:", data);
}
}
Why This Operator is a Game-Changer
There are many situations where we want to handle exceptions without creating a mutable variable outside the try-catch block. Here’s how we might traditionally handle such a scenario:
let result;
try {
result = await fetch("https://api.example.com/data");
} catch (error) {
result = null; // Handle the error
}
The above approach is far from ideal, especially if you’re striving for immutable code. You’d have to change your const to let, which disrupts immutability.
With ?=, we retain immutability while simplifying error handling:
const [err, res] ?= await fetch(“https://api.example.com/data”);
The operator ensures that both the error and the result are handled in a single, concise statement.
How Does ?= Work?
Under the hood, the ?= operator leverages a custom implementation using Symbol.result. This symbol is added to Function.prototype and Promise.prototype to provide seamless error handling.
Here’s the polyfill implementation:
Symbol.result = Symbol("result");
Function.prototype[Symbol.result] = function (...args) {
try {
const result = this.apply(this, args);
if (result && typeof result === "object" && Symbol.result in result) {
return result[Symbol.result]();
}
return [null, result];
} catch (error) {
return [error || new Error("Thrown error is falsy")];
}
}
Promise.prototype[Symbol.result] = async function () {
try {
const result = await this;
return [null, result];
} catch (error) {
return [error || new Error("Thrown error is falsy")];
}
};
Polyfill in Action
Let’s see how the polyfill works with both synchronous and asynchronous cases.
Synchronous Function Example
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}
const [err, result] = divide[Symbol.result](10, 2);
if (err) {
console.error("Error:", err.message);
} else {
console.log("Result:", result);
}
Asynchronous Example with fs.readFile
const fs = require("fs").promises
async function readFile(filePath) {
const [err, data] = await fs.readFile(filePath, "utf-8")[Symbol.result]();
if (err) {
console.error("Error reading file:", err.message);
} else {
console.log("File content:", data);
}
}
How to Start Using It Today
While the ?= operator is not yet natively supported in JavaScript, you can start experimenting with it using a polyfill. Here’s how:
Step 1: Add the Polyfill
Symbol.result = (fn) => {
return async (...args) => {
try {
return [null, await fn(...args)];
} catch (err) {
return [err, null];
}
};
};
Step 2: Use It in Your Code
const fetchSafe = Symbol.result(fetch)
const [err, res] = await fetchSafe("https://api.example.com/data");
if (err) {
console.error("Error:", err);
} else {
console.log("Success:", res);
}
Final Thoughts
The safe assignment operator (?=) is a welcome addition to JavaScript, simplifying error handling and improving code readability. While it’s not yet natively available, you can start using it today with the polyfill.
Whether you’re handling API calls, file operations, or synchronous computations, ?= makes your code cleaner, more intuitive, and less error-prone.
Bonus: Every Crazy Thing JavaScript Does
Love diving into the quirks of JavaScript? Check out Every Crazy Thing JavaScript Does, a guide that unpacks subtle caveats and lesser-known features of the language. You’ll discover tips to avoid bugs and write more efficient code.
What do you think about the ?= operator?
