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: 'codingbeautydev.com',
};
console.log({ ...info, theme: 'π΅' });
// { name: 'Coding Beauty',
// site: '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 ?
<!pattern
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.
Every Crazy Thing JavaScript Does
A captivating guide to the subtle caveats and lesser-known parts of JavaScript.