Categories
JavaScript

JavaScript Best Practice — Replacing Old with New

Spread the love

Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are a lot of tricky parts to JavaScript, and we can follow some best practices to improve our code.

Since ES6 was introduced, new constructs are replacing older ones for good reasons. It’s much shorter, cleaner, and easier to understand.

In this article, we’ll look at which older constructs that can be replaced with new ones, including replacing then with async and await, replacing dictionary objects with Maps, replacing apply with the spread operator, and replacing function constructors with the class syntax.

Replace Then with Async / Await

When we chain promises, we used to do it by using the then method and then returning another promise in the callback function that we pass into then.

This means that we have code that looks something like this:

Promise.resolve(1)  
  .then((val) => {  
    console.log(val);  
    return Promise.resolve(2);  
  })  
  .then((val) => {  
    console.log(val);  
    return Promise.resolve(3);  
  })  
  .then((val) => {  
    console.log(val);  
  })

With the introduction of the async and await syntax, which is just a shorthand for calling the then method repeatedly. We can write the code above like this:

(async () => {  
  const val1 = await Promise.resolve(1);  
  console.log(val1);  
  const val2 = await Promise.resolve(2);  
  console.log(val2);  
  const val3 = await Promise.resolve(3);  
  console.log(val3);  
})();

They both output 1, 2 and 3, but the second code snippet is so much shorter. It’s so much clearer that there’s no reason to go back to the old syntax.

Also, we can loop through promises and run them one after the other by using the for-await-of loop. We can loop through the promises we have above by rewriting the code like the following:

(async () => {  
  const promises = [  
    Promise.resolve(1),  
    Promise.resolve(2),  
    Promise.resolve(3),  
  ] 

  for await (let p of promises) {  
    const val = await p;  
    console.log(val);  
  }  
})();

Code like the one we have above is very handy for looping through many promises or promises that are created on the fly, which we can’t do in the earlier examples.

Replacing Dictionary Objects with Maps

ES6 also introduced the Map constructor, which lets us create hash maps or dictionaries without using JavaScript objects with string keys to do so.

Maps are also better because they have their own methods to get and manipulate the entries.

For example, instead of writing:

const dict = {  
  'foo': 1,  
  'bar': 2  
}

We write:

const dict = new Map([  
  ['foo', 1],  
  ['bar', 2]  
])

Then we can get an entry by using the get method as follows:

console.log(dict.get('foo'));

We can set the value of an existing entry by the key of the entry with the set method:

dict.set('foo', 2);

Also, we can check if an entry exists with the given key with the has method:

dict.has('baz');

There are also the keys and entries methods to get all the keys of the map and all the entries respectively.

For example, we can write:

console.log(dict.keys());

To get an iterator object with the keys of the Map . This means that we can loop through them with the for...of loop or convert it to an array with the spread operator.

Similarly, the entries method returns an iterator object with all the entries in the Map with each entry being an array of [key, value] .

There’s also the value method to get an iterator object with all the values in the Map .

We can also use other primitive values as keys. If we use objects, we can’t get the value back when we look them up since the lookup is done with the === operator which returns false is 2 objects that don’t have the same reference even if they have the same content.

These methods aren’t available in a regular object. Also, we might accidentally get or modify the object’s prototype’s properties instead of its own if we use the for...in loop.

Therefore, there aren’t many reasons to use a regular object as a dictionary anymore.

Replace Apply with Spread

If we don’t want to change the value of this inside the function, there isn’t much reason to use the apply method in functions.

If we only want to call a function with many arguments, we can use the spread operator when passing in an array for our arguments as follows:

const arr = [1, 2, 3, 4, 5];  
const add = (...args) => args.reduce((a, b) => a + b, 0);  
console.log(add(...arr));

All we have to do is to use the spread operator on our array, which is the 3 dots operator in the last line, and then the array will be separated into a comma-separated list of arguments.

Replace Constructor Functions with Classes

Another great ES6 feature is the class syntax for constructor functions. It’s simply syntactic sugar that makes the constructor function looks like a class.

The advantage of it is that it makes inheritance easy.

For example, if we want to inherit from a constructor function, we have to write something like this:

function Person(name) {  
  this.name = name;  
}

function Employee(name, employeeCode) {  
  Person.call(this, name);  
  Employee.prototype.constructor = Person;  
  this.employeeCode = employeeCode;  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

This syntax looks strange coming from class-based object-oriented languages like Java.

However, the class syntax makes things look a lot familiar to developers that used other languages more than JavaScript. We can rewrite the code above to the following:

class Person {  
  constructor(name) {  
    this.name = name;  
  }  
}

class Employee extends Person {  
  constructor(name, employeecode) {  
    super(name);  
    this.employeecode = employeecode;  
  }  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

The code does the same thing as what we have above. However, it’s clearer what we’re inheriting from since we have the extends keyword to indicate what we’re inheriting from.

With the constructor function, we have to worry about the value of this in the first argument of the call method, and what we pass into the subsequent arguments of call.

With the class syntax, we don’t have to worry about this. If we forgot to make the super call like the following code:

class Person {  
  constructor(name) {  
    this.name = name;  
  }  
}

class Employee extends Person {  
  constructor(name, employeecode) {     
    this.employeecode = employeecode;  
  }  
}

const employee = new Employee('Joe', 1);  
console.log(employee)

We’ll get the error ‘Uncaught ReferenceError: Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor.’

It’s one less chance of making a mistake.

We don’t get any error if we omit the Person.call like in the Employee constructor function since the browser doesn’t know we want Employee to inherit from Person .

In addition, when we log the prototype of employee , we get that the prototype of employee is Person as expected with the class syntax.

However, we don’t get that unless we put Employee.prototype.constructor = Person; which is easily forgotten.

Conclusion

async and await and for-await-of are new constructs that make chaining promises much cleaner. It’s much better to use them instead of using then because of it.

for-await-of also lets us loop through promises that are generated dynamically which we can’t do with then or async and await alone.

Map s are much better than plain objects for hashes and dictionaries because it has its own methods to manipulate and get the entries. Also, we may accidentally be accessing the properties of prototype of plain objects.

If we don’t want to change the value of this inside a function, we can replace the apply method for calling functions with an array of arguments with the spread operator, which does the same thing.

Finally, the class syntax for constructors is much better than the original function syntax since we can inherit from parent classes easier than setting the prototype constructor of a constructor function.

Leave a Reply

Your email address will not be published. Required fields are marked *