im

9 neat ES features that save you lots of typing

A short article showing how Javascript has evolved for the better in recent years.

Believe it or not, many of the Javascript syntax features that developers take for granted today did not exist in the language just a few years ago.

Yes, even promises are new. Javascript use to be a verbose language with lots of quirks and it still is. For example, are you sure you understand how this works? Do you know why you sometimes need to use bind method? What about scoping and hoisting?

I started programming in Javascript professionally when jQuery was cool, Node.js was just announced on HackerNews and all the cool devs were working with Backbone.js.

It was not all roses and sunshine. Trust me. Callback Hell and Pyramid of Doom were real. Sometimes I dreamed about double-callbacks at night. It was very hard following the code at times.

Thankfully, things have greatly improved since, but we as developers always take things for granted without giving them any thought or appreciation.

That's why I decided to show what Javascript used to look like back in the days, (old school) and how much it has evolved in just a few years (new school).

Without further ado, here are nine of the ES features I am glad exist.

  1. Arrow functions
  2. Default parameters
  3. Destructuring
  4. Object literals
  5. Default returns
  6. Spread operator
  7. Async/await
  8. Optional chaining
  9. Nullish Coalescing Operator

There features save my fingers from hurting and make my code more concise and easier to understand.

Read on to learn them all!

1. Arrow functions#

This is probably the ES feature I use and love the most. It makes code so much easier to read in my opinion.

// old school

function fullname(user) {
return `${user.firstName} + ${user.lastName}`;
}

// or
const fullname = function () {
return `${user.firstName} + ${user.lastName}`;
}

document.addEventListener('click', function (event) {
alert('old school');
});

// new school

const fullname = user => `${user.firstName} + ${user.lastName}`;

document.addEventListener('click', event => {
alert('new school');
});

// or just this

document.addEventListener('click', () => alert('new school'));

Note: Something to keep in the back of the head, when using arrow functions, is that this works just as you expect it to inside them. Or maybe better to say: arrow functions don't have their own this. It's taken from the outer scope instead. Sometimes it's actually not what you want.

Pretty neat and much easier to read. Don't you agree?

2. Default Parameters#

Default parameters is a real life-saver sometimes. A simple code example is the best explanation.

// old school

const createUser = function (firstName, lastName, role) {
return {
firstName: firstName,
lastName: lastName,
role: role || 'user'
};
}

// new school

const createUser = (firstName, lastName, role = 'user') => ({
firstName,
lastName,
role,
});

Less code, less logic and reads much better, doesn't it?

3. Destructuring#

This is a really beautiful ES feature. It works with objects, arrays and function parameters.

Desctructuring lets you extract one or more properties from arrays and objects into own variables.

const user = { firstName: 'Jane', lastName: 'Doe', role: 'admin' };

// extract role property to a variable
const { role } = user;

console.log(role);
// admin

You have probably used it many times in your import statements without giving it much thought.

You can use destructuring with arrays too. If you worked with React hooks, then you have definitely used it.


// Naïve state hook implementation
// Don't use in production!
const state = {};

const useState = (name, initial) => {
state[name] = initial;

// return an array
return [
state[name],
value => (state[name] = value)
];
};

// returns an array
const loggedInState = useState('loggedIn', false);

// old school

const isLoggedIn = loggedInState[0];
const setLoginStatus = loggedInState[1];

// new school

// assign array[0] and array[1] to variables
const [isLoggedIn, setLoginStatus] = useState('loggedIn', false);

if (isLoggedIn) {
setLoginStatus(false);
}

It works with function parameters too. Very nice!

// instead of this
let isAdmin = user => user.role === 'admin';

// you can do this
isAdmin = ({ role }) => role === 'admin';

// and also combine it with default parameters
isAdmin = ({ role = 'user' }) => role === 'admin';

const user = { uid: 'jdoe', role: 'admin' };
console.log(isAdmin(user));
// true

There are other useful things you can do with desctructuring, such as rest parameters. I encourage you to explore it further on your own. You will find a list of resources at the end of the article.

4. Object Literals#

The name "object literal" says nada. That's why I like to call it object property initializer shorthand. Maybe that's even its official name. Not sure.

Anyhow, it's one of the features that can save you a lot of repetitive typing. If the variable name and property name are the same you only have to specify variable name when creating an object's property.

// old school

function createUser(firstName, lastName, dob) {
const id = generateId();
return {
id: id,
firstName: firstName,
lastName, lastName,
dob: dob
};
}

// new school

const createUser = (firstName, lastName, dob) =>
({ id: generateId(), firstName, lastName, dob });

Just look at how many potential typos and untyped characters we just saved!

You can also use the shorthand operator with methods on the object.

// old school

function createUser(firstName, lastName, dob) {
return {
firstName: firstName,
lastName, lastName,
dob: dob,
fullName: function () {
return firstName + ' ' + lastName;
}
};
}

// new school

const createUser = (firstName, lastName, dob) => ({
firstName,
lastName,
dob,
fullName() {
return `${this.firstName} ${this.lastName}`;
},
});

I don't know about you, but I find this feature very useful.

5. Default Returns#

I've already sneaked in default returns in some of the examples above, but it's not something that existed in Javascript before. Believe it or not.

const arr = [1, 2, 3, 4, 5];

// old school

const doubled = arr.map(function (val) {
return val * 2;
});

// new school with default implicit return

const tripled = arr.map(val => val * 3);

Default returns also lets you return objects directly.

// old school

function createUser(firstName, lastName) {
return {
firstName: firstName,
lastName: lastName,
role: 'user'
};
}

// new school

const createUser = (firstName, lastName) => ({ firstName, lastName });

Don't take simple things like this for granted. Be thankful for all the typing they save you in the long run.

6. Spread Operator#

I actually not sure if it's an operator or a syntax change, but I use it all the time.

It's a very useful feature and if you have worked with Redux or any other modern Javascript library you have probably used the pattern, or operator, or syntax.

It often used to create shallow copies of objects and arrays, or to merge objects or arrays.

Let's start with the arrays, since A comes first in the English alphabet.


const one = ['a', 'b', 'c'];
const two = ['d', 'e', 'f'];

// old school

// copy array
const copy = one.slice(0);

// combine arrays
const combined = one.concat(two);

// new school

// copy array
const copy = [...one];

// combine arrays
const combined = [...one, ...two];

Now, here is what we can do with spread operator and objects.


let user = { uid: 'jdoe', name: 'Jane Doe' };
let status = { loggedIn: true };

// create a new object by combining other objects
// properties with the same name will be overwritten
// by the last object in argument chain
let state = Object.assign({}, user, status, { createdAt: Date.now() });

// or by simply using the spread operator
state = { ...user, ...status, createdAt: Date.now() };

Spread operator (or syntax) is very versatile. I only scratched the surface of what it can do.

If you learn the inside and outs of it, you will have a powerful tool in your toolbox.

How could I even work effectively before it existed? I actually don't remember right now, but I suspect it was painful.

7. async/await#

Promises are also new to Javascript and they might be the most significant new addition to the language. Before promises we had callbacks, which could lead to Callback Hell and the dreaded Pyramid of Doom.

Promises improved that, but I still found them a little hard to read. Today, I often find myself reaching for the async/await pattern, which makes my code easy to read and reason about.

Plus, async/await pattern can make the code easier to structure.


// fake function to return a user by id
const fetchUser = id => new Promise((resolve, reject) => {
if (id === 1) {
resolve({ id: 1, username: 'jdoe' });
} else {
reject(new Error('no such user'));
}
});

// the promise way

const getUserInfo = id =>
fetchUser(id)
.then(user => fetchInfomation(user))
.then(userInfo => {
if (userInfo.address) {
// how do we get user? we need to pass it down somehow
enrichProfile(user, userInfo);
}

if (userInfo.age && userInfo.age < 18) {
// hmm ... how do we access user here?
addAgeRestrictions(user);
}
})
.catch(err => console.log(err));

// the async/await way

const getUserInfo = async id => {
// error handling with try/catch blocks
try {
const user = await fetchUser(id);
const userInfo = await fetchInformation(user);

// both user and userInfo are available in the function scope
if (userInfo.address) {
enrichProfile(user, userInfo);
}

if (userInfo.age && userInfo.age < 18) {
addAgeRestrictions(user);
}
} catch (err) {
console.log(err.message);
throw err;
}
}

As you can see above, prefixing your function with async keyword and using await to get the promise result can make your code easier to structure, read and reason about.

8. Optional Chaining (ES2020)#

This new feature is my recent favorite as it saves me a lot of typing. It actually comes from Typescript, but has now been accepted into ECMAScript standard spec.

You will not get runtime errors if some deeply-nested property does not exist on the object.

Errors like the one below, that you have probably seen many times before.

const customer = { name: 'Jane' };

console.log(customer.address.doorCode);
// Uncaught TypeError: Cannot read property 'doorCode' of undefined

Here is a better example of what this operator brings to the table (or screen).

// old school

const sendInstructions = function (customer) {
// old, cumbersome way to check for a property
const hasDoorCode =
customer && customer.address && customer.address.doorCode;

if (hasDoorCode) {
messageToCourier(carryIn());
}
}

// new school

const sendInstructions = customer => {
// optional chaining operator at work
const hasDoorCode = customer?.address?.doorCode;

if (hasDoorCode) {
messageToCourier(carryIn());
}
}

Simply explained, the optional chaining operator short circuits and returns undefined for the first non-existent property.

9. Nullish Coalescing Operator (ES2020)#

This operator with a weird name is new, so thread carefully and use a bundler setup that supports it.

What it does it to strictly check if the value is either null or undefined. Why is that important? Because one of Javascript's quirks is that many values can be falsy or truthy. Empty string, zero and boolean false are all falsy values, so is undefined and null.

Sometimes we need to strictly check if value is null or undefined and not only falsy.

Let me demonstrate with a few examples to make it more clear.


const user = {
name: 'John Doe',
settings: { showWelcomeScreen: false, animation: 0 },
};

// old school

// the actual value is false, but it incorrectly defaults to true,
// which can lead to hard to find bugs
const showWelcomeScreen =
(user && user.settings && user.settings.showWelcomeScreen) || true;

// the animation value is actually 0, but we incorrectly set it to 100
// since 0 is a falsy value in Javascript
const duration = (user && user.settings && user.settings.animation) || 100;

// new school

// this behavior is correct. We now only set a value if
// the property is null or undefined
const showWelcomeScreen = user?.settings?.showWelcomeScreen ?? true;
const animation = user?.settings?.animation ?? 100;

Even though this operator is very new, I find myself using it more often. It's really useful when dealing with user-controlled settings in user interfaces.

Conclusion#

There you go. 9 Javascript features I am glad exist.

Of course, there are many more new cool and useful ES features, but these are the ones I use the most.

I strongly recommend you to get familiar with them all. They will make you level-up your game and help you impress your colleagues.

If you want to dig deeper, here are some great learning resources:

Even though these new syntax features and operators make your code more concise, always remember the trade-off between conciseness and readability. It's you who might be reading the code in the future.