Categories
JavaScript Best Practices

JavaScript Best Practices — Classes and Functions

Cleaning up our JavaScript code is easy with default parameters and property shorthands.

In this article, we’ll look at the best practices when we create classes and functions.

Avoid Creating God Classes

God classes are classes that are all-knowing. If a class is mostly used for retrieving data from other classes using get and set methods, then we don’t need the class.

So if we have something like this:

class Foo {
  getBar() {}
  setBar() {}
  getBaz() {}
  setBaz() {}
  getQux() {}
  setQux() {}
}

We can remove the class, and access the things we need directly.

Eliminate Irrelevant Classes

If we need a class, then we should remove it.

This applies to classes that only have data. If we have members that are in the class, then we should consider if they should be members of another class instead.

Avoid Classes Named After Verbs

We shouldn’t name classes with verbs since a class that has only behavior but not data shouldn’t be a class.

For instance, if we have:

class GetFoo {
  //...
}

Then it should be a function instead of a class.

When Should We Create Functions?

We should create functions to make our code better. The following are the reasons for creating functions.

Reduce Complexity

Reducing complexity is one of the most important reasons for creating functions.

We got to create them so that we don’t repeat code and minimizing code size.

If we write all procedures without functions, then we would have lots of similar code.

For instance, if we have:

const greet1 = `hi joe`;
const greet2 = `hi jane`;

Then we can make a function to generalize that:

const greet = (name) => `hi ${name}`;
const greet1 = greet('joe');
const greet2 = greet('jane');

Now we can call greet anywhere to create similar strings.

Introduce an Intermediate, Understandable Abstraction

The code above is also an abstraction. We generalized the code for returning greeting strings.

We can pass in different values of name and return a new string.

Avoid Duplicate Code

Avoiding duplicate code is also a good reason for creating functions.

Again, as we can see from the example above, we can call greet to create many more of the same strings.

Then we don’t have to repeat the hi part everywhere.

Support Subclassing

We can create a method in a subclass to override a method in the superclass.

For instance, we can write the following code to do that in JavaScript:

class Animal {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} speaks`;
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }
  speak() {
    return `${super.speak(this.name)} in dog language`;
  }
}

We have overridden the speak method in Dog by calling super.speak .

Therefore, we can keep the speak method in Dog simpler.

Photo by Danielle Cerullo on Unsplash

Hide Sequences

Functions are good for hiding implementations of procedures.

For instance, we can have a function that calls other functions and do something with them.

We can write:

const calcWeight = () => {
  //...
}

const calcHeight = () => {
  //...
}

const calcWidth = () => {
  //...
}

const getAllDimensions = () => {
  const weight = calcWeight();
  //...
  const height = calcHeight();
  //...
  const width = calcWidth();
  //...
}

We called multiple functions in the code above. But other parts of our program won’t know the order that they’re called.

This reduces the implementation that is exposed and reduces coupling.

Improve Portability

Functions can be moved anywhere easily. We can move it and anything else that depends on it easily.

Simplify Complicated Boolean Tests

If we have long boolean expressions, then we should put them into their own function.

For instance, if we have:

const isWindowsIE = navigator.userAgent.toLowerCase().includes('windows') &&
  navigator.userAgent.toLowerCase().includes('ie');

Then we can put the whole thing into a function by writing:

const isWindowsIE = () => {
  const userAgent = navigator.userAgent.toLowerCase()
  return userAgent.includes('windows') &&
    userAgent.includes('ie');
}

It’s just much cleaner than having long boolean expressions.

Conclusion

God classes, classes that only data or behavior are all bad. We should make sure that we have a mix before creating both.

We create functions to reduce complexity, remove duplicate code, and make abstractions.

They’re good for creating anything.

Categories
JavaScript Best Practices

JavaScript Best Practices — Improving Classes

Cleaning up our JavaScript code is easy with default parameters and property shorthands.

In this article, we’ll look at the best practices for creating classes and when we should create them.

Constructors

There are a few things that we should do to make our constructors better. They are the following.

Initialize All Member Data in All Constructors If Possible

We should put them all in the constructor so that they’re all initialized when we instantiate the object.

So we can write:

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

Now we make sure that everything’s initialized with a value.

Create a Singleton In the Constructor

If we need only one instance of a constructor, then we can create one instance of it.

For instance, we can write the following:

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

In the code above, we return the object that we created if this.instance isn’t defined yet.

Otherwise, we return whatever it’s set to this.instance .

Prefer Deep Copies to Shallow Copies Until Proven Otherwise

Deep copies copy everything, so that’s a lot better than doing a shallow copy. Shallow copies leave some things referencing the original object.

That’s not good if we want a true copy.

Therefore, we’ve to make our code to make deep copies as follows:

const copy = obj => {
  const copied = {
    ...obj
  };
  for (const k of Object.keys(obj)) {
    if (typeof obj[k] === 'object') {
      copied[k] = {
        ...copied[k]
      };
      copy(copied[k]);
    }
  }
  return copied;
}

We just use the spread operator to copy nested objects if one is found. And do the same thing recursively.

Then we return the object that we copied.

When Should We Create a Class?

We shouldn’t always create classes. There a few scenarios where it makes sense to create a class.

Model Real-World Objects

Classes are great for modeling real-world objects since they model the behavior of objects

They let us encapsulate instance variables and methods into one package to store state and do actions on objects respectively.

Model Abstract Objects

Likewise, we can use classes to model abstract objects.

They can be used to make abstractions, which are generalizations of different kinds of objects.

Classes are great for holding shared members of subclasses. And subclasses can inherit from them.

However, we should keep the inheritance tree simple so that people won’t be confused with the code.

Reduce Complexity

We can use classes to reduce the complexity of a program.

Classes are great for hiding information. In JavaScript, there’re no private variables in classes yet, so we’ve to hide data in methods instead.

We can then minimize coupling between different parts of our program with that.

Hide Implementation Details

Methods are also good for hiding implementation details.

We can hide the details within methods and only run things that are needed.

To do that, we can nest functions and variables inside methods.

Limit Effects of Changes

The effects of changes can be reduced since we can hide things.

As with hiding implementation, the effects of changes can be isolated by limiting the effects of changes within methods.

Hide Global Data

Global data can become private data by putting them inside the methods of a class.

Then they don’t have to be exposed to the public. All we have to do is to use let and const to declare them within methods.

Streamline Parameter Passing

If we have the same parameters passed into different functions, then we can change the parameters to instance variables and the functions to methods.

For instance, if we have:

const speak = (name) => `${name} spoke`;
const greet = (name) => `hi, ${name}`;

Then we can put the methods into their own class as follows:

class Person {
  constructor(name) {
    this.name = name;
  }
  speak() {
    return `${this.name} spoke`;
  }
  greet() {
    return `hi, ${this.name}`;
  }
}

Now we don’t have to pass in name everwhere.

We just make an instance of Person and call those methods without passing in any arguments.

Conclusion

We can create classes to encapsulate data and package things together. However, we shouldn’t create classes for everything.

Also, we should make deep copies rather than shallow copies whenever possible.

Categories
JavaScript Best Practices

JavaScript Best Practices — Classes

Cleaning up our JavaScript code is easy with default parameters and property shorthands.

In this article, we’ll look at the best practices for creating classes.


ES6 Class Basics

To make the prototypical inheritance model of JavaScript less confusing, the class syntax has been introduced in ES6.

It has the constructor , instance variables, and methods just like classes in languages like Java do, but they act differently.

For instance, we can create a class by writing:

We have the name instance variable and the speak method. this is the instance of the class itself.


Getters and Setters

Getters are methods that only return values, whereas setters are methods that let us set a value to an instance variable.

They let us do either in a controlled manner rather than accessing instance variables directly.

We can also add getters and setters to the class as follows:

With these getters and setters, we can write the following to get name:

const person = new Person('joe');
console.log(person.name);

And set name:

const person = new Person('joe');
person.name = 'jane';
console.log(person.name);

Child or Subclasses

We can also create child classes with the extends keyword. For instance, we can write:

We created a Dog class that overrides the speak method of the Animal class.

Also, we call the parent constructor with super in the Dog’s constructor and call super.speak to call the speak method of Animal.

This is much cleaner than the old constructor function syntax for inheritance.


Static Methods and Properties

We can add static properties to a class by using the static keyword.

Doing that will help us add a property to it that’s shared by all instances.

For instance, we can write:

class Dog {
  static type() {
    return 'dog';
  }
}

Then we can call it as a static method as follows:

console.log(Dog.type())

To create static properties, we write the following:

class Dog {}
Dog.type = 'dog';

console.log(Dog.type);

We set the type property to 'dog' and it’s shared across all instances.

This syntax is valid since classes are just functions and functions are treated the same as any other object in JavaScript.


Private Fields

Future versions of JavaScript can also let us define private class fields. There’s no equivalent of this in constructor functions.

They are denoted by the # symbol in front of the field name.


Conclusion

We can define constructors with the class syntax to make them look easier to understand than constructor functions.

The classes look similar to Java classes but act very differently underneath. We have to understand the underlying behavior before we use it. This means that we should understand prototypical inheritance before using JavaScript classes.

Categories
JavaScript Best Practices

JavaScript Best Practices — Promises

Some ways of writing JavaScript code are better than others.

In this article, we’ll look at best practices for using the latest JavaScript promise features.


Converting Non-Promise Async Code to Promises

We can convert the non-promise async code to promises with the util.promisify() method. This lets us convert many functions with async callbacks with the signature of the form (err, res), where err is an object with errors and res has the results of the async code.

Any function that has a promising form defined for it can also use this utility to return the promise form. As long as the promise version is stored as the value of the symbol property util.promisify.custom , we can get the promise version.

For instance, since setTimeout has a promise-based version in the standard library of Node.js, we can convert it to a promise as follows:

const util = require('util');
const sleep = util.promisify(setTimeout);

(async () => {
  await sleep(1000);
  console.log('slept');
})()

Now we have a sleep function that pauses execution for the time we want, instead of having to nest callbacks.


Asynchronous Chaining

In the old way, we chain promises with the then method.

For instance, we write:

promise1
  .then((res) => {
    //...
    return promise2
  })
  .then((res) => {
    //...
    return promise3
  })
  .then((res) => {
    //...
  })

We can clean this up with async and await as follows:

(async () => {
  const val1 = await promise1;
  //...
  const val2 = await promise2;
  //...
  const val3 = await promise3;
  //...
})();

This is much cleaner as the resolve promise values are assigned to the constants or variables on the left side.


Using Catch to Catch Errors

As with synchronous code, we have to catch errors and handle them gracefully.

To do that with promise code, we can call the catch method. For instance:

promise1
  .then((res) => {
    //...
    return promise2
  })
  .then((res) => {
    //...
    return promise3
  })
  .then((res) => {
    //...
  })
  .catch((err) => {
    //...
  })

If we use async and await , then we use try...catch as we do with synchronous code, as follows:

(async () => {
  try {
    const val1 = await promise1;
    //...
    const val2 = await promise2;
    //...
    const val3 = await promise3;
    //...
  } catch (ex) {
    //...
  }
})();

The code in catch in both examples would run when the first promise is rejected.


Use Finally to Run Code That Should Run Regardless of the Outcome of the Promises

If we need to run code that should be run regardless of the outcomes of the promise chain, then we should use the finally method or the finally block for async and await code.

For instance, we can run clean up code in finally as follows:

promise1
  .then((res) => {
    //...
    return promise2
  })
  .then((res) => {
    //...
    return promise3
  })
  .then((res) => {
    //...
  })
  .finally((err) => {
    //...
  })

Or we can write:

(async () => {
  try {
    const val1 = await promise1;
    //...
    const val2 = await promise2;
    //...
    const val3 = await promise3;
    //...
  } catch (ex) {
    console.log(ex);
  } finally {
    //...
  }
})();

Multiple Asynchronous Calls with Promise.all()

If we want to run multiple unrelated promises at once, then we should use Promise.all.

For instance, we can run them all at once as follows:

Promise.all([
    promise1,
    promise2,
    promise3,
  ])
  .then((res) => {
    //...
  })

If we use async and await, we can write:

(async () => {
  const [val1, val2, val3] = await Promise.all([
    promise1,
    promise2,
    promise3,
  ])
})();

In both examples, the resolved values of all three promises are either in res or assigned to an array on the left (in the second example).

This way, they run all at once instead of waiting for each to resolve.


Summary

  • We can clean up our async code by using promises.
  • Converting to async code to promises can be done with Node apps by using the util.promisify method.
  • If we want to chain promises, before sure to add the catch method or block to handle errors gracefully.
  • finally method or block can be used to run code that’s always run regardless of the promises outcomes.
  • Promise.all is great for running unrelated promises all at once.
Categories
JavaScript Best Practices

JavaScript Best Practices — Modern Syntax

There are better ways to write JavaScript code than others.

In this article, we’ll look at the best practices for using the latest JavaScript syntax features.

Syntactic Sugar

In JavaScript, the syntactic sugar that’s included is usually good.

They let us write code in a shorter and clear way and make them more readable in the process.

These syntactic sugars are drop-in replacements for existing techniques so we should use them.

const Isn’t Consistent

const is a keyword for declaring block-scoped constants.

They’ll make primitive values assigned to it immutable since we can’t assign a constant again after it’s assigned to a value.

A value must be assigned when we declare it.

However, const is a bit deceptive since some people might not know that we can still change objects that are assigned to const by changing their properties.

Also, arrays can be changed in place with methods like push and unshift .

Therefore, we shouldn’t assume that objects that are assigned to const are immutable.

Limiting the Scope of the Function

Traditional functions defined with the functionm keyword can be called to run statements defined in the block and may return a value.

They can be run anywhere if they’re written as a function declaration.

For instance, if we have:

function foo() {
  //...
}

Then foo can be run before or after it’s defined.

It also defines its own this and can be used with the new operator as a constructor.

So, to limit the capabilities of our functions, we should use arrow functions.

If we need constructors, then we should use the class syntax to define them to make everyone clear.

The Class Syntax

The class syntax is great for defining constructors. It does the same thing as the old constructor function syntax.

It looks like a class in an object-oriented language like Java, but it does the same thing as JavaScript constructors.

Therefore, the prototypical inheritance model is still used with JavaScript classes.

So the following:

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

Person.prototype.greet = function() {
  console.log(`hi ${this.name}`);
};

is the same as:

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

  greet() {
    console.log(`hi ${this.name}`);
  }
}

They hold and do the same thing but the placement of the fields and methods are different.

Arrow Functions

Arrow functions are great. They are shorter. Thet but more neatly in callbacks.

It can return without adding the return keyword if we return in the first line.

They encapsulate qualities that make them more convenient. But they aren’t a drop-in replacement for traditional functions defined with the function keyword.

We can call bind by to change this inside a function.

Also, we can’t call call and apply with them to change this and call functions with arguments with them.

Going Asynchronous

Going async with JavaScript has its own difficulty because of JavaScript’s single-threaded nature.

We’ve to write code that unblocks the thread so that they won’t hold up our programs until we’re ready to run them.

This is where async code with callbacks come in to make calling async code easier.

We can make our code async by calling setTimeout . Also, pretty much any HTTP client runs in an async manner.

If we have lots of async code with callbacks, then we have to nest callbacks.

This gets ugly real fast.

If we use callbacks for chaining async code, we may have code that looks like this:

async1((err, res) => {
  if (!err) {
    async2(res, (err, res) => {
      if (!err) {
        async3(res, (err, res) => {
          //...
        });
      }
    });
  }
});

That’s ugly and just not maintainable. Instead, we use promises. Then we can write something like:

promise1
  .then((res) => {
    //...
    return promise2
  })
  .then((res) => {
    //...
    return promise3
  })
  .then((res) => {
    //...
  })

That’s much cleaner than nesting async callbacks with the previous example.

We can make things even shorter by writing:

(async () => {
  const val1 = await promise1;
  //...
  const val2 = await promise2;
  //...
  const val3 = await promise3;
  //...
})();

As we can see, the code is much shorter and it’s exactly the same as the promise chain we have before.

The only difference is that va11 , val2 , and val3 hold the resolved values of the promises instead of res .

Conclusion

We got to know some things about JavaScript like how const aren’t always immutable, and clean up async code with async and await.

Also, arrow functions and classes should be used for regular functions and constructors respectively.