Categories
JavaScript Best Practices

JavaScript Best Practices — Arrays, Todos, and Callbacks

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Add Expiration Conditions to TODO Comments

If we have todo comments, we can add expiration conditions to it so that ESLint will pick them up and throw an error if they expired.

For instance, we can write:

// TODO [2019-11-15]: fix this

We have the date in ISO 8601 format and the task description.

Explicitly Comparing the length Property of a Value

Explicitly comparing the length property of value is clearer than not.

For instance, instead of writing:

if (string.length) {}
if (array.length) {}

We write:

if (string.length > 0) {}
if (array.length > 0) {}

to compare them against 0 explicitly.

Case Style for Filenames

The case style for filenames should be consistent.

For instance, we can use kebab case for everything:

foo-bar.js

Or we can use camel case.

For instance, we can write:

fooBar.js

Or we can use snake case:

foo_bar.js

Or we can use pascal case:

FooBar.js

Importing Index Files with .

If we’re importing an index.js file, we don’t need to write the file name explicitly.

For instance, instead of writing:

const module = require('./index');

or:

const module= require('./');

We write:

const m = require('.');

or:

const m = require('./foo');

Usenew for all Built-in Constructors, Except String, Number and Boolean

Almost all constructors need the new keyword except for the String , Number , and Boolean ,

We shouldn’t use constructors top create primitive values

Instead, we use them without new :

const str = String(123);

But we write:

const list = new Array(10);

This is because using new with String , Number , or Boolean create objects instead of primitive values.

Use Array.isArray() instead of instanceof Array

Array.isArray is more robust that instanceof Array .

instanceof Array doesn’t work across realms or context.

It doesn’t work between different frames or windows in browsers or the vm module in Node.

For instance, instead of writing:

array instanceof Array;

We write:

Array.isArray(array);

No Leading Space Between console.log Parameters

We don’t need leading space between console.log parameters.

We just put the trailing space.

So instead of writing:

console.log('abc' , 'def');

We write:

console.log('abc', 'def');

Passing a Function Reference Directly to Iterator Methods

We should create our own callback instead of passing the function reference it directly.

For instance, instead of writing:

[1, 2, 3].map(fn);

We write:

[1, 2, 3].map(c => fn(c)));

This way, we can control what parameters are passed in to fn instead of always passing the array entry as the first argument, the index as the 2nd argument, and the array as the 3rd argument.

This also applies to any other function call that takes callbacks.

We want to control the arguments we pass in by creating our own function,

Do not use a for Loop that can be Replaced with a for-of Loop

If we can use a for-of loop to iterate through an object, then we should do so.

For instance, instead of writing:

for (let index = 0; index < arr.length; index++) {
  const element = arr[index];
  console.log(element);
}

We write:

for (const element of array) {
  console.log(element);
}

It’s much cleaner.

Use Unicode Escapes Instead of Hexadecimal Escapes

We should use Unicode escapes instead of hex escapes for clarity and consistency.

For instance, instead of writing:

const foo = 'x1B';

We write:

const foo = 'u001B';

No Nested Ternary Expressions

Nested ternary expressions are hard to read, so we shouldn’t have them.

For instance, instead of writing:

const foo = i > 39 ? i < 100 ? true : true: false;

We wrote:

const foo = (x === 1) ? 'foo' : 'bar';

Conclusion

We should use Unicode escapes instead of hex escapes in a regex.

Ternary expressions are hard to read, so we shouldn’t have them.

ESLint can check for the expiration date of todo comments.

File names casing should be consistent.

We should define our own callbacks instead of passing in a function reference.

The for-of loop is much better than a for loop for looping through iterable objects.

Categories
JavaScript Best Practices

JavaScript Best Practices — Spread, Rest, and Promises

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Replace the Use of the RegExp Constructor in favor of Regular Expression Literals

Regex literals are much shorter than using the RegExp constructor.

So we should use them in favor of the constructor.

For instance, instead of writing:

new RegExp("^\d.$");

We write:

/^\d.$"/

Using the Rest Parameters Instead of arguments

The argyments object doesn’t work with arrow functions and it’s not an array.

Rest parameters return an array of the arguments.

So we should use that instead.

For instance, instead of writing:

function foo() {
  console.log(arguments);
}

We write:

function foo(...args) {
  console.log(args);
}

Use Spread Syntax Instead of .apply()

We should use the spread syntax instead of apply .

For instance, instead of writing:

const args = [1, 2, 3, 100, 200\];
Math.max.apply(Math, args);

We write:

const args = [1, 2, 3, 100, 200];
Math.max(...args);

We don’t need apply unless we want to change the value of this .

Use Template Literals Instead of String Concatenation

We should use template literals instead of string concatenation.

It’s much easier to include expressions in template literals.

For instance, instead of writing:

const str = "hello, " + name + "!";

We write:

const str = `hello, ${name}!`;

Return Inside Each then() to Create Readable and Reusable Promise Chains

We should return something if we want to call then on the promise.

For instance, instead of writing:

myPromise.then(() => {
  doSomething()
})

We write:

myPromise.then(() => {
  return doSomething()
})

Use catch() on Un-returned Promises

We should use catch on unreturned promises to catch any errors to occur with them.

For instance, we can write:

myPromise
  .then(doSomething)
  .then(doMore)
  .catch(errors)

where errors is a callback to catch any error that occurred from the promises.

Make Sure to Create a Promise Constructor Before Using it in an ES5 Environment

The Promise constructor is introduced with ES6.

So any code written with older versions of ES must use a promise library to create promises.

For instance, instead of writing:

const x = Promise.resolve('foo');

We write:

const Promise = require('bluebird');
const x = Promise.resolve('foo');

No Nested then() or catch() Statements

We should avoid nested then or catch statements.

For instance, instead of writing:

myPromise.then(val =>
  doWork(val).then(doMore)
)

We write:

myPromise
  .then(doWork)
  .then(doMore)
  .catch(errors)

Avoid Using new on a Promise Static Method

If we are calling Promise static methods, then we shouldn’; use the new keyword.

For instance, instead of writing:

new Promise.resolve(value)
new Promise.reject(error)
new Promise.race([foo, bar])
new Promise.all([foo, bar])

We write:

Promise.resolve(value)
Promise.reject(error)
Promise.race([foo, bar])
Promise.all([foo, bar])

No return Statements in finally()

return statements in finally is useless since nothing will consume the result.

Therefore, instead of writing:

myPromise.finally((val) => {
  return val
})

We write:

myPromise.finally((val) => {
  console.log(val)
})

No Wrapping Values in Promise.resolve or Promise.reject when not Needed

We shouldn’t wrap values in Promise.resolve or Promis.reject when they aren’t needed.

For instance, instead of writing:

myPromise.then((val) => {
  return Promise.resolve(val * 3);
})
myPromise.then((val) => {
  return Promise.reject('foo');
})

We write:

myPromise.then((val) => {
  return val * 3
})
myPromise.then((val) => {
  throw 'foo'
})

return will return a promise with the resolved value being the return value.

throw will reject a promise with the given value.

Consistent Parameter Names when Creating New Promises

We should have consistent parameter names when creating new promises.

This way, there won’t be any confusion as to what they do.

For instance, instead of writing:

new Promise((reject, resolve) => { ... })

which have the functions in the wrong order or:

new Promise((ok, fail) => { ... })

which have nonstandard names, we write:

new Promise((resolve, reject) => { ... })

Prefer async/await to the Callback Pattern

async and await is shorter than the callback pattern, so we can sue that instead.

For instance, instead of writing:

myPromise
  .then((val) => {
    return foo();
  })
  .then((val) => {
    return bar();
  })
  .then((val) => {
    return val;
  })

We write:

const foo = async () => {
  const val1 = await myPromise;
  const val2 = await foo();
  const val3 = await bar();
  return va3;
}

Conclusion

We should use spread and rest instead of apply and arguments .

Promises can be cleaned up in many ways.

Categories
JavaScript Best Practices

JavaScript Best Practices — Spaces, Functions, Negations

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

No Functions in Loops

Functions in loops result in errors because of how they’re created in a loop.

With var , the loop index is always the last since it doesn’t get passed into the variable function until it’s done.

Instead of writing:

for (var i = 0; i < 10; i++) {
  funcs[i] = function() {
    return i;
  };
}

We write:

for (let i = 0; i < 10; i++) {
  funcs[i] = function() {
    return i;
  };
}

to get the expected value of i in the function returned, which is the index value of the loop during each iteration.

With var , the value will always be 10.

No Magic Numbers

Magic numbers are hard to understand and we’ve to change them in multiple places, so we should assign them to named constants.

Instead of writing:

const now = Date.now();
const anHourLater = now + (60 * 60 * 1000);

We write:

const SECONDS_IN_HOUR = 60 * 60;

No Characters which are Made with Multiple Code Points in Character Class Syntax

We shouldn’t have characters that are made of multiple code points.

This is because they’ll be matched to either character.

So w should write:

/^[a]$/u.test("a")

instead of:

/^[Á]$/u.test("Á")

No Mixes of Different Operators

Mixing different operators are confusing, so we shouldn’t do them.

For instance, instead of writing:

const foo = a && b || c || d;

We put in some parentheses to separate the expressions:

const foo = (a && b) || c || d;

require Calls to be Mixed with Regular Variable Declarations

Instead of writing require calls separated with regular variables with commas, we just write them all in their own line for clarity.

For instance, instead of writing:

const eventEmitter = require('events').EventEmitter,
  foo = 10,
  bar = 'baz';

We write:

const eventEmitter = require('events').EventEmitter;
const foo = 10;
let bar = 'baz';

No Mixing Spaces and Tabs for Indentation

Mixing spaces and tabs for indentation will cause issues with parsing and formatting with text editors, so we should convert tabs into spaces.

No Use of Chained Assignment Expressions

Chained assignment expressions will create global variables.

The ones that aren’t next to the variable declaration keyword will be global.

They’re also hard to read.

For instance, if we have:

const foo = bar = 0;

then bar is global and foo is constant.

Instead, we separate them for clarity:

const foo = -0;
const bar = 0;

No Multiple Spaces

One space is enough for anything other than indentation.

So we should keep a single space for expressions.

Instead of writing:

if(foo  === "baz") {}

We write:

if(foo === "baz") {}

No Multiline Strings

We shouldn’t write multiline strings with a backslash.

It’s not standard and it’s bad.

So instead of writing:

let x = "line 1
  line 2";

We write:

let x = `line 1
  line 2`;

We use template strings instead of regular strings for multiline strings.

No Multiple Empty Lines

Multiple empty lines aren’t useful for separating code.

We just need one.

So instead of writing:

let foo = 5;

var bar = 3;

We write:

let foo = 5;

var bar = 3;

No Reassignment of Native Objects

We should never reassign native objects to anything else.

We don’t want any unexpected results because of that.

So instead of writing:

Object = null
undefined = 1

We write:

let foo = null;
let bar = 1;

No Negated Conditions

Negated conditions are hard to read, so we should avoid them as much as possible.

For instance, instead of writing:

if (!a) {
  doSomething();
} else {
  doMore();
}

We write:

if (a) {
  doSomething();
} else {
  doMore();
}

Conclusion

We don’t need multiple spaces in our code.

Negated conditions should be replaced with positive ones.

Magic numbers should be replaced with named constants.

Be careful that we don’t have regex with characters that have multiple code points.

Spaces and tabs shouldn’t be mixed.

Categories
JavaScript Best Practices

JavaScript Best Practices — Duplicates, Returns, Eval, and Built-in Prototypes

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

No Duplicate Conditions in if-else-if Chains

We shouldn’t have duplicate conditions in if-else-if chains.

Only the first one will run.

So instead of writing:

if (isSomething(x)) {
  foo();
} else if (isSomething(x)) {
  bar();
}

We write:

if (isSomething(x)) {
  foo();
} else if (isSomethingElse(x)) {
  bar();
}

No Duplicate Keys in Object Literals

We shouldn’t have duplicate keys in object literals.

For instance, we shouldn’t have objects like:

const foo = {
  bar: "baz",
  bar: "bar"
};

Instead, we write:

const foo = {
  bar: "baz"
};

No Duplicate Case Label

We shouldn’t have duplicate case labels in our code.

For instance, instead of writing:

switch (a) {
  case 1:
    break;
  case 2:
    break;
  case 1:
    break;
  default:
    break;
}

We write:

switch (a) {
  case 1:
    break;
  case 2:
    break;
  default:
    break;
}

No Duplicate Imports

We shouldn’t have multiple, import statements for one module.

For instance, instead of writing:

import { merge } from 'module';
import something from 'foo';
import { find } from 'module';

We write:

import { merge, find } from 'module';
import something from 'foo';

No return Before else

If we’re writing a return statement, then we can remove the else

For instance, we can write:

function foo() {
  if (x) {
    return y;
  }
  return z;
}

instead of writing:

function foo() {
  if (x) {
    return y;
  } else {
    return z;
  }
}

No Empty Block Statements

Empty blocks aren’t very useful, so we should fill them with something or remove them.

For instance, the following aren’t very useful:

if (bar) {}

while (bar) {}

switch (bar) {}

try {
  doSomething();
} catch (ex) {

} finally {

}

for (;;){}

Instead, we fill them with something.

Empty Character Classes in Regex

We shouldn’t have empty character classes in regex since they don’t match anything.

For instance, we should have code like:

/^foo[]/.test("foobar");
"foobar".match(/^foo[]/);

Instead, we should fill the brackets with a pattern:

/^foo[a-z]/.test("foobar");
"foobar".match(/^foo[a-z]/);

No Empty Functions

We shouldn’t have empty functions in our code.

They don’t do anything.

For instance, instead of writing:

function foo() {

}

We write:

function foo() {
  doSomething();
}

No Empty Destructuring Patterns

Empty destructuring patterns do nothing, and they’re easily mistaken for empty objects as the default value.

For instance:

const {a: {}} = foo;

is easily mistaken for:

const {a = {}} = foo;

The first is an empty destructuring pattern.

The 2nd is using the empty object as the default value.

No Null Comparisons

Comparing null using == or != can have unintended consequences because of the data type coercion.

Therefore, we should use === or !== to do the comparison.

Instead of writing:

if (foo == null) {
  bar();
}

while (qux != null) {
  bar();
}

We write:

if (foo === null) {
  bar();
}

while (qux !== null) {
  bar();
}

No Calls to eval()

eval lets us run JavaScript code from a string.

But we shouldn’t use it since it’s insecure to run code from a string.

Performance optimizations also can’t be done on code that’s in a string.

Instead of writing:

eval("let a = 0");

We write:

let a = 0;

No Reassigning Exceptions in catch Clauses

We shouldn’t reassign exceptions ib catch clauses.

If we do, then we lose data from the exception.

So instead of writing;

try {
  // code
} catch (e) {
  e = 10;
}

We write:

try {
  // code
} catch (e) {
  const foo = 1;
}

Don’t Extend Native Objects

We shouldn’t extend built-in objects, even though we’re allowed to.

For instance, we shouldn’t write code like:

Object.prototype.foo = 55;

to add properties to a built-in constructor.

Using defineProperty is also bad:

Object.defineProperty(Array.prototype, "bar", { value: 999 });

Conclusion

We shouldn’t have duplicate conditions, keys, or case labels.

Also, we should never use eval .

And we shouldn’t extend native objects.

Empty destructuring patterns are also useless and deceptive.

We should compare null with === or !== .

Categories
JavaScript Best Practices

JavaScript Best Practices — Comments, Nesting, and Parameters

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

No Commented Out Code in our Codebase

We shouldn’t have commented out code in our codebase.

We have version control to keep a record of our changes.

So we should remove them.

If we have:

doStuff();
// doMoreStuff();

Then we should rewrite it to:

doStuff();

No Journal Comments

We shouldn’t have journal comments since version control has the code change records.

For instance, we shouldn’t write:

/**
 * 2019-02-03: Removed type-checking
 * 2018-03-14: Added add function
 */
function add(a, b) {
  return a + b;
}

We write:

function add(a, b) {
  return a + b;
}

No Positional Markers

Positional markers are just noise, we shouldn’t have them in our code.

Instead of writing:

////////////////////////////////////////////////////////////////////////////////
// initialize setup data
////////////////////////////////////////////////////////////////////////////////
const init = function() {
  // ...
};

We write:

const init = function() {
  // ...
};

Maximum Number of Classes Per File

We may want to enforce a maximum number of classes per file to reduce a file’s complexity and responsibility.

For instance, we may limit the number of classes to 3 max:

class Foo {}
class Bar {}
class Baz {}

Maximum Depth that Blocks can be Nested

Nested code is hard to read.

Therefore, we should eliminate them as much as possible.

One or 2 levels of nesting are probably the max that can be tolerated.

For instance, don’t write

function foo() {
  for (;;) {
    while (true) {
      if (true) {
        if (true) {
        }
      }
    }
  }
}

Instead, we write:

function foo() {
  for (;;) {

  }
}

We can use guard clauses to return early.

For instance, we can write:

function foo() {
  if (!cond) {
    return;
  }
  while (true) {

  }
}

instead of:

function foo() {
  if (!cond) {
    while (true) {

    }
  }
}

We can also use break or continue to stop a loop or move to the next iteration of it.

Maximum Line Length

We’ve to scroll horizontally to read long lines.

Therefore, we shouldn’t write them.

For instance, we shouldn’t write:

const foo = { "foo": "This is a foo.", "bar": { "qux": "This is a bar"}, "baz": "This is a baz" };

It overflows the line.

Instead, we write:

const foo = {
  "foo": "This is a foo.",
  "bar": {
    "qux": "This is a bar"
  },
  "baz": "This is a baz"
};

Each line is much shorter and easier to read.

We can do the same with comments.

Maximum File Length

Big files are complex and hard to understand.

Therefore, we should divide them into smaller files.

300 to 500 lines max is usually the recommendation.

Maximum Function Length

We should make sure our function only does one thing.

Therefore, we should reduce the length of our functions.

For instance, we can write:

function foo() {
  let x = 0;
}

which is a short, simple function.

Maximum Depth that Callbacks can be Nested

We often have to run callbacks for both synchronous and async code.

We’ve to run many of them to do all the things we want.

The worst way to write them is to nest them deeply.

So we shouldn’t write things like:

foo(function() {
  bar(function() {
    qux(function() {
      abc(function() {

      });
    });
  });
});

That’s just very hard to read and debug, especially if we have lots of code in each callback.

We should use alternatives like promises.

Maximum Number of Parameters in Function Definitions

We shouldn’t have too many parameters in a function.

The more we have, the harder it is to read.

For instance, instead of writing:

function foo(bar, baz, qux, abc) {
  doSomething();
}

We write:

function foo({ bar, baz, qux, abc }) {
  doSomething();
}

If we need lots of parameters, we can take an object and destructure them.

Conclusion

We shouldn’t have commented out code or other useless comments.

Nesting, function length, and the number of parameters should be minimized.

The number of classes should also be reduced.

A file shouldn’t have too many lines of code.