The 5 most transformative JavaScript features from ES9
JavaScript has come a long way in the past 10 years with brand new feature upgrades in each one.
Let’s look at the 5 most significant features that arrived in ES9; and see the ones you missed.
1. Async generation and iteration
Async generators was a powerful one from ES9.
Just like normal generators but now it pops out the values after asynchronous work like a network request or something:
function* asyncGenerator() {
yield new Promise((resolve) =>
setTimeout(() => resolve('done this ✅'), 2000)
);
yield new Promise((resolve) =>
setTimeout(() => resolve('done that ✅'), 3000)
);
}
So when we call .next()
we'll get a Promise
:
const asyncGen = asyncGenerator();
asyncGen.next().value.then(console.log);
asyncGen.next().value.then(console.log);
It's such a powerful tool for streaming data in web app in a structured + readable manner — just look at this function that buffers and streams data for a video-sharing app like YouTube:
async function* streamVideo({ id }) {
let endOfVideo = false;
const downloadChunk = async (sizeInBytes) => {
const response = await fetch(
`api.example.com/videos/${id}`
);
const { chunk, done } = await response.json();
if (done) endOfVideo = true;
return chunk;
};
while (!endOfVideo) {
const bufferSize = 500 * 1024 * 1024;
yield await downloadChunk(bufferSize);
}
}
And now to consume this generator we'll use for await of
-- async iteration:
for await (const chunk of streamVideo({ id: 2341 })) {
// process video chunk
}
I wonder if the actual YouTube JavaScript code uses generators like this?
2. Rest / spread operator
No doubt you've stumbled upon the modern spread syntax somewhere.
A genius way to rapidly and immutable clone arrays:
const colors = ['🔴', '🔵', '🟡'];
console.log([...colors, '🟢']);
// [ '🔴', '🔵', '🟡', '🟢' ]
We never had it before ES9, and now it's all over the place.
Redux is big one:
export default function userState(state = initialUserState, action){
console.log(arr);
switch (action.type){
case ADD_ITEM :
return {
...state,
arr:[...state.arr, action.newItem]
}
default:return state
}
}
And it works for objects too:
const info = {
name: 'Coding Beauty',
site: 'wp.codingbeautydev.com',
};
console.log({ ...info, theme: '🔵' });
// { name: 'Coding Beauty',
// site: 'wp.codingbeautydev.com',
// theme: '🔵' }
Overrides props:
const langs = {
j: 'java',
c: 'c++',
};
console.log({ ...langs, j: 'javascript ' });
// { j: 'javascript ', c: 'c++' }
This makes it especially great for building upon default values, especially when making a public utility.
Or customizing a default theme like I did with Material UI:
With the spread syntax you can even scoop out an object's copy without properties you don't want.
const colors = {
yellow: '🟡',
blue: '🔵',
red: '🔴',
};
const { yellow, ...withoutYellow } = colors;
console.log(withoutYellow);
// { blue: '🔵', red: '🔴' }
That's how you remove properties from an object immutably.
3. String.raw
When I use String.raw I'm saying: Just give me what I give you. Don't process anything.
Leave those escape characters alone:
// For some weird reason you can use it without brackets
// like this 👇
const message = String.raw`\n is for newline and \t is for tab`;
console.log(message); // \n is for newline and \t is for tab
No more escaping backslashes:
const filePath = 'C:\\Code\\JavaScript\\tests\\index.js';
console.log(`The file path is ${filePath}`);
// The file path is C:\Code\JavaScript\tests\index.js
We can write:
const filePath = String.raw`C:\Code\JavaScript\tests\index.js`;
console.log(`The file path is ${filePath}`);
// The file path is C:\Code\JavaScript\tests\index.js
Perfect for writing regexes with a stupid amount of these backslashes:
Something like this but much worse:
From this✅:
const patternString = 'The (\\w+) is (\\d+)';
const pattern = new RegExp(patternString);
const message = 'The number is 100';
console.log(pattern.exec(message));
// ['The number is 100', 'number', '100']
To this✅:
const patternString = String.raw`The (\w+) is (\d+)`;
const pattern = new RegExp(patternString);
const message = 'The number is 100';
console.log(pattern.exec(message));
// ['The number is 100', 'number', '100']
So "raw" as in unprocessed.
That's why we have String.raw()
but no String.cooked()
.
4. Sophisticated regex features
And speaking of regexes ES9 didn't disappoint.
It came fully loaded with state-of-the-art regex features for advanced string searching and replacing.
Look-behind assertions
This was a new feature to make sure that only a certain pattern comes before what you're searching for:
- Positive look-behind: Whitelist
?<=pattern
- Negative look-behind: Blacklist ?
const str = "It's just $5, and I have €20 and £50";
// Only match number sequence if $ comes first
const regexPos = /(?<=\$)\d+/g;
console.log(str.match(regexPos)); // ['5']
const regexNeg = /(?<!\$)(\d+)/g; // ['20', '50' ]
console.log(str.match(regexNeg));
Named capture groups
Capture groups has always been one of the most invaluable regex features for transforming strings in complex ways.
const str = 'The cat sat on a map';
// $1 -> [a-z]
// $2 -> a
// $3 -> t
// () indicates group
str.replace(/([a-z])(a)(t)/g, '$1*$3');
// -> The c*t s*t on a map
So normally the groups go by their relative position in the regex: 1, 2, 3...
But this made understanding and changing those stupidly long regexes much harder.
So ES9 solved this with ?<name>
to name capture groups:
const str = 'The cat sat on a map';
// left & right
console.log(str.replace(/(?<left>[a-z])(a)(?<right>t)/g, '$<left>*$<right>'));
// -> The c*t s*t on a map
You know how when things break in VS Code, you can quickly Alt + Click to go to the exact point where it happened? 👇
VS Code uses capture groups to make the filenames clickable and make this rapid navigation possible.
I'd say it's something like this:
// The stupidly long regex
const regex =
/(?<path>[a-z]:(?:(?:\/|(?:\\?))[\w \.-]+)+):(?<line>\d+):(?<char>\d+)/gi;
// ✅ String.raw!
const filePoint = String.raw`C:\coding-beauty\coding-beauty-javascript\index.js:3:5`;
const extractor =
/(?<path>[a-z]:(?:(?:\/|(?:\\?))[\w \.-]+)+):(?<line>\d+):(?<char>\d+)/i;
const [path, lineStr, charStr] = filePoint
.match(regex)[0]
.match(extractor)
.slice(1, 4);
const line = Number(lineStr);
const char = Number(charStr);
console.log({ path, line, char });
// Replace all filePoint with <button> tag
// <button onclick="navigateWithButtonFilepointInnerText">{filePoint}</button>
5. Promise.finally
Finally we have Promise.finally 😉.
You know how finally
always run some code whether errors are there or not?
function startBodyBuilding() {
if (Math.random() > 0.5) {
throw new Error("I'm tired😫");
}
console.log('Off to the gym 👟💪');
}
try {
startBodyBuilding();
} catch {
console.log('Stopped excuse🛑');
} finally {
console.log("I'm going!🏃");
}
So Promise.finally
is just like that but for async tasks:
async function startBodyBuilding() {
await think();
if (Math.random() > 0.5) {
throw new Error("I'm tired😫");
}
console.log('Off to the gym 👟💪');
}
startBodyBuilding()
.then(() => {
console.log('Started ✅');
})
.catch(() => {
console.log('No excuses');
})
.finally(() => {
console.log("I'm going!🏃");
});
The biggest pro of Promise.finally()
is when you're chaining lots of Promises:
It also works well with Promise chains:
getFruitApiUrl().then((url) => {
return fetch(url)
.then((res) => res.json())
.then((data) => {
fruits.push(data);
})
.catch((err) => {
console.error(err);
})
.finally(() => {
console.log(fruits);
});
});
Brought forth by ES9.
Final thoughts
ES9 marked a significant leap forward for JavaScript with several features that have become essential for modern development.
Empowering you to write cleaner code with greater conciseness, expressiveness, and clarity.
See also
- The 5 most transformative JavaScript features from ES12
- The 5 most transformative JavaScript features from ES13
- The 5 most transformative JavaScript features from ES8
- The 5 most transformative JavaScript features from ES14
- The 7 most transformative JavaScript features from ES10
- 5 amazing new JavaScript features in ES15 (2024)