5 JavaScript features you (probably) haven’t used

Peterkwkwan
CodeX
Published in
5 min readFeb 14, 2023

The 2022 The State of JavaScript survey came out a few weeks ago. The survey has proven to be a fascinating litmus test of where JavaScript is headed, as well as a breeding ground for developers to vent their frustration towards Angular.

Google — please shutdown Angular like you do with all your other projects

One particularly interesting section highlights new(ish) JavaScript features which many developers haven’t used or haven’t heard of. If you are reading this, chances are, you probably don’t know some of these features either (don’t worry, me too).

Nani??

So, instead of being intimidated by new tools and terminology (I certainly am!), I’ll aim to give you a TLDR (too long; didn’t read) version of 5 lesser-known features in JavaScript.

5) Promise.allSettled() (37.7% never heard of it)

Promise.allSettled() is a static method that accepts an iterable (e.g. array) of promises. Example below:

const promise1 = Promise.resolve(1)
const promise2 = Promise.reject(new Error("This was rejected!"))
const promise3 = Promise.resolve(5)
const allPromises = [promise1, promise2, promise3]

Promise.allSettled(allPromises).then(
results =>
// perform additional logic here
results.forEach(result => {
console.log(result)
})
)

// Output:
// {status: 'fulfilled', value: 1}
// {status: 'rejected', reason: Error: This was rejected!}
// {status: 'fulfilled', value: 5}

The main use case for Promise.allSettled() is for handling multiple independent asynchronous tasks. This allows you to know the result of each task, and follow up with any additional logic.

This is similar to Promise.all(), the main difference being that Promise.all() immediately rejects when any one of the promises reject. If we replace our above code with Promise.all() instead, we will get the follow output:

const promise1 = Promise.resolve(1)
const promise2 = Promise.reject(new Error("This was rejected!"))
// ···

Promise.all(allPromises).then(
results =>
results.forEach(result => {
console.log(result)
}),
error => {
// promise2 rejects
// thus, promise.all() will immediately reject -> handle error here
console.log(`At least one promise failed! \n ${error}`)
}
)

// Output:
// At least one promise failed!
// Error: This was rejected!

Use Promise.allSettled() when:

  • you need to know the final outcome of all the promises
  • tasks are independent from one another (no dependent logic)

Use Promise.all() when:

  • you want to immediately reject upon any of the promises rejecting
  • tasks are dependent upon each other

4) Numeric Separators (49.7% never heard of it)

Numeric Separators allows us to use underscores (_, U+005F) to write large numbers in a more human-friendly way. Attach underscores to help break up long numbers, especially when encountering many repeating digits.

Highlighted below are some valid usages of the numeric separator.

1_000_000_000_000
1_050.95
0b1010_0001_1000_0101 // A binary integer literal with its bits grouped per nibble:
0xA0_B0_C0 // A hexadecimal integer literal with its digits grouped by byte
1_000_000_000_000_000_000_000n // A BigInt literal with its digits grouped per thousand

In this example, we use numeric separators as a thousand separator:


const randomNum = 1000 + 1_000
console.log(randomNum) // 2000

const anotherRandomNum = 3_001
console.log(randomNum + anotherRandomNum) // 5001

Quite handy I must say!

3) Logical Assignment (45.8% never heard of it)

Note: assumes knowledge of logical AND short-circuiting evaluation

Logical Assignment (&&=) is represented by this syntax:

x &&= y

It will assign the value of y into x only if x is truthy.

The logical AND portion of the syntax will short-circuit, meaning it can be rewritten in a longer syntax as:

x && (x = y);

Putting it into practice below:

const scores = {
react: 95,
vue: 90,
angular: 0,
};

console.log(scores.react); // 95

// Changing the value using logical AND assignment operator
scores.react &&= 100;

// Value changed because scores.react is truthy
console.log(scores.react); // 100

console.log(scores.angular); // 0

scores.angular &&= 50;

// Value remains unchanged because scores.angular is falsy
console.log(scores.angular); // 0

2) Temporal (73.2% never heard of it)

Temporal is (yet) another date/time API for JavaScript. However, this is an interesting one, because it is currently (as of time of writing) at a stage 3 proposal, meaning it will likely become part of the official JavaScript standard in the near future.

Using Dates in JavaScript has always been a pain, that is why many date/time libraries currently thrive in the ecosystem (Luxon, Date-fns, Datejs, to name a few).

Temporal aims to rectify this, using a global Object that acts as a namespace (similar to Math), and brings a modern, easy-to-use date/time API to JavaScript out-of-the-box. This means we should no longer need any external date/time libraries (yay!).

I won’t go into detail since the API docs is quite lengthy, but feel free to check it out here.

1) Error.prototype.cause (72.7% never heard of it)

This one is handy for Node.js developers — this feature allows us to attach a specific cause to our error, leading to easier debugging.

Usually, debugging is a pain because we have no idea where to start! Having an “error cause” helps provide more context around the origin of the error. By chaining the error, we can catch and re-throw an error, producing a more-specific error log while still having access to the original error.

// method #1
function readFiles(filePath) {
try {
const text = readText(filePath)
const json = JSON.parse(text)
return processJson(json)
} catch (error) {
throw new Error(`Error while processing ${filePath}`)
}
}

// method #2
function readFilesWithErrorCause(filePath) {
try {
// ···
} catch (error) {
throw new Error(`Error while processing ${filePath}`, { cause: error })
}
}

In method #1, our error message will look something like this:

Error: Error while processing [FILEPATH]

In method #2, our enhanced error message will appear like this:

Error: Error while processing [FILEPATH]
[cause]: Error: Cannot open file [FILEPATH]: No such file or directory

Attaching a cause behind the error provides additional context around the error, as well as improving the readability of the error log. This is especially helpful when you have multiple errors and it becomes hard to identify where one error starts and the other ends.

This was a very simple example. You can use it for more complex error handling by adding multiple error levels, leading to your error logs looking like this:

Error: An error has occured while trying to execute apiFetch
[cause]: Error: An error has occured while trying to fetch getCustomerById
[cause]: Error: 401 - Unauthorized - Token Expired

Feel free to experiment!

TL;DR

I hope this has helped you gain a few new tools in your JS arsenal. If you already knew all 5 of these, then congrats! You’re officially a Git Master.

--

--

Peterkwkwan
CodeX
Writer for

Hey! I'm a self-taught developer with over 4 years of frontend experience. I enjoy writing about tech, frontend tools and software engineering as a career.