Categories
JavaScript Best Practices

JavaScript Best Practices- Loops and Useless Expressions

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at loops that we shouldn’t have, and useless labels, catch clauses, and call and apply calls that are useless.

No Unmodified Loop Conditions

If we have a while or do...while loop with an unmodified condition, the loop will run forever as the loop condition hasn’t been updated with a new value.

This is usually not what we want as it’ll crash our program most of the time because of the infinite loop.

However, infinite loops are sometimes appropriate for functions like generator functions that return the latest value when the generator is created from the function and invoked.

Therefore, in a regular while loop, we probably shouldn’t write an infinite loop:

while (condition) {
    doSomething();
}

However, it’s appropriate for generator functions that return an infinite number of entries:

function* num() {
  let i = 1;
  while (true) {
    yield i;
    i++;
  }
}

const iterator = num();
const val = iterator.next().value;
const val2 = iterator.next().value;

In the code above, we have the num generator function while returns a new integer after we created an iterator from it by calling it, and then calling next on it one or more times to get the value one at a time.

For regular while or do...while loops, we should almost always have a condition that changes.

For instance, we can write the following:

let x = 1;
while (x <= 5) {
  console.log(x);
  x++;
}

The code above has a condition that ends the loop as we increase x by 1 until x is 5.

No Unused Expressions

Some JavaScript expressions in our code are useless as it doesn’t do anything. For instance, if we have the following code:

x + 1;

Then it doesn’t do anything since we didn’t assign it to a variable and we didn’t return it in a function.

With build tools, expressions like these may be eliminated and break application logic.

Also, sequence assignment expressions like x = 1, y = 2 are always useless unless we return the values, assign them to another variable somewhere else, or do some manipulation with it later.

No Unused Labels

Unused labels are useless since they aren’t used anywhere. Therefore it’s dead code that’s just taking up space.

For instance, if we have the following:

loop:
  for (let i = 1; i <= 5; i++) {
    console.log(i);
  }

Either we should use it or we should remove it. We can use it as follows, for example:

loop:
  for (let i = 1; i <= 5; i++) {
    console.log(i);
    if (i === 2) {
      break loop;
    }
  }

We break the loop with the loop label with break loop when i is 2.

No Unnecessary .call() and .apply()

The call and apply method is only useful when we need to change the value of this in a function and call it with various arguments.

The following uses for it aren’t very useful:

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

greet.call(undefined, 'hi');

We passed in undefined to make this set to undefined . So it’s the same as calling greet('hi') directly.

Therefore, we don’t need to invoke the call method.

Likewise, the following apply call is also useless:

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

greet.apply(undefined, ['hi']);

It’s also the calling greet('hi') directly.

Instead, we should call greet with call or apply in ways that are useful.

For instance, we should call them as follows:

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

greet.apply({
  name: 'jane'
}, ['hi']);

greet.call({
  name: 'jane'
}, 'hi');

This way, we set the value of this inside the function to an object with the name property, so that this.name inside the function can be populated with the this.name .

We actually need to use call and apply to set the value of this in the example above.

Conclusion

Unmodified loop conditions are bad most of the time because it may cause crashes because of the infinite loop.

However, infinite loops are useful in generator functions that keep returning values with the returned iterator.

Unused labels for loops are useless and so it’s dead code that should be removed.

call and apply that are useless, like those that set the value of this to undefined since they are no different than calling the function directly.

Finally, we also shouldn’t have unused expressions in our code since it’s just more useless code that we can remove.

Categories
JavaScript Best Practices

JavaScript Best Practices — Generators and Object Properties

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at the best practices when using generators and defining and using object properties.

Don’t Use Generators If We Want To Transpile to ES5

Generator code doesn’t transpile well to ES5 code, so we may want to think twice if we’re targeting our build artifact to build into ES5 code.

However, this shouldn’t be the case with modern transpilers since they create custom closure based state machines from generators and async functions. They work the same way. However, the only advantage is that the transpile code is harder to debug even with source maps.

Therefore, if we never have to debug the transpiled code, then we can go ahead and use generators. Otherwise, we should think twice before using them in our code.

Make Sure Generator Function Signature is Spaced Properly

Generator functions should be spaced properly. The asterisk for the generator function should come right after the function keyword.

For instance, we should define our function as follows:

const foo = function*() {
  yield 1;
}

In the code above, we have the asterisk coming right after the function keyword. And there’s no space after the asterisk.

For function declarations, we should write the following:

function* foo() {
  yield 1;
}

In the code above, we have one asterisk after the asterisk and before the function name.

Use Dot Notation When Accessing Properties

If an object property’s name is a valid JavaScript identifier, then we should use the dot notation to access the object property.

It’s shorter than the bracket notation and does the same thing.

For instance, instead of writing the following with bracket notation:

const obj = {
  foo: 1
}

console.log(obj['foo']);

We should write the following code:

const obj = {
  foo: 1
}

console.log(obj.foo);

In the first example, we used the bracket notation, which is longer and we have to access the foo property by passing in a string into the brackets. We have to write extra characters just to access the foo property.

Instead, we should write what we have in the 2nd example, which is obj.foo .

It’s shorter and does the same thing.

Use Bracket Notation [] When Accessing Properties With a Variable

If we want to access a property with the name that’s stored in the variable, then we should use the bracket notation to do that.

For instance, we can do that with the following code:

const obj = {
  foo: 1
}

const getProp = (prop) => {
  return obj[prop];
}

console.log(getProp('foo'));

In the code above, we have the obj object with the foo property. Also, we have the getProp function, which takes the prop parameter and returns the value of obj[prop] .

We’ve to access the property value with the bracket notation since prop is a variable, so there’s no other way to access the property dynamically.

Then in the last line of our example, we can use getProp as follows:

getProp('foo')

to return the value of obj.foo which is 1.

Use Exponentiation Operator ** When Calculating Exponentiations

The exponentiation operator provides us with a shorter way to calculate exponents. It’s shorter than Math.pow which is available since the first version of JavaScript.

The exponentiation operator is available since ES6. For instance, instead of writing the following with Math.pow :

const result = Math.pow(2, 5);

We should write:

const result = 2 ** 5;

It’s much shorter and we don’t have to call a function to do exponentiation anymore.

Conclusion

If we want to transpile our code to ES5 and debug the ES5 code that’s built, then we shouldn’t use JavaScript generators in our code.

Debugging is hard even with source maps.

If we do use generators in our code, then we should make sure the generator code is spaced properly. The * should be spaced in a standard for consistency and easiness to read.

When accessing object properties that are valid JavaScript identifiers, then we should use the dot notation. Otherwise, we should use the bracket notation. This includes accessing properties with variables and property names that aren’t valid JavaScript identifiers.

Categories
JavaScript Best Practices

JavaScript Best Practices — Conditionals

JavaScript is a very forgiving language. It’s easy to write code that runs but has issues in it.

In this article, we’ll look at how we should write conditional statements in our JavaScript programs.

Write the Normal Path Through the Code First

We should take into account the normal paths first, then write the unusual cases.

So we should check the cases that are encounter more frequently than the rare edge cases.

Make Sure that we Branch Correctly on Equality

>= is different from > and also <= is different from < .

Therefore, we should make sure to avoid off-by-one errors by using the right one.

Put the Normal Case After the if Rather than After the else

The normal case is better after the if because it lets us run less code than if we put it in the else .

Short circuit evaluation means that we just run the if statement rather than checking the if statement and then going to the else .

If most cases are successes, then we should put the success case after the if rather than putting it after the else .

For instance, instead of writing:

if (status === 'error') {
  //...
} else if (status === 'success') {
  //...
} else {
  //...
}

We flip the cases around by writing:

if (status === 'success') {
  //...
} else if (status === 'error') {
  //...
} else {
  //...
}

With the error case on top, we can skip all the error cases that we’ve to check for if the status is 'success' .

We also know that the if case is always the normal successful case if we follow this convention.

Follow the if Clause with a Meaningful Statement

We shouldn’t have an if block that does nothing. So instead of writing:

if (condition) {

} else {
  // ...
}

We should instead write:

if (!condition) {
  //...
}

Consider the else Clause

We can use the else clause so that we take into conditions that aren’t taken into account for in the if block.

For instance, we may want to write:

if (MIN <= value && value <= MAX) {
  // do something ...
} else {
  // invalid value, do nothing
}

We may want to have the else block explains why we do nothing if the condition in the if isn’t met.

This way, we make sure that we didn’t ignore the case opposite of the condition in the if block.

Test the else Clause for Correctness

We want to test all cases for correctness in our program.

So we should test the else clause in addition to the if clause.

Check for Reversal of the if and else Clauses

We get unexpected results if we flip the if and else clauses.

Therefore, we should check if those clauses are flipped around.

Simplify Complicated Tests with Boolean Function Calls

If we have complex tests in our conditionals, we should put the expressions in a function so that we can just call the function.

The function name also makes everyone clear what the check is doing.

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

const inRange = (min, max, value) => (min <= value && value <= max);
if (inRange(min, max, value)) {
  // ...
} else {
  //...
}

We put the range checking code into a function and then called that.

Now we know that the boolean expressions are checking if value is in the given range of numbers as bound by min and max .

Put the Most Common Cases First

Since the most common cases are encountered the most frequently, we should put them first so most of the time, the less frequently encountered cases don’t have to be checked.

For instance, if letters are the most commonly encountered characters in ou if statement, then we can put that as the first case:

if (isLetter(input)) {
  //...
} else if (isNumber(input)) {
  ///...
} else {
  //...
}

This way, the first case will be the most common one and our program has to run the else clauses less often.

Conclusion

We can write conditional statements that are more efficient by putting the most frequently encountered cases first.

Also, we want to put complex boolean expressions into their own functions to make them clearer.

The normal case should go first since it’s encountered more frequently.

We should also use the right operator for comparison to reduce off-by-one errors.

Categories
JavaScript Best Practices

JavaScript Best Practices— Useless Catch, Concatenation, Escape, Return and More

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at more useless code that we should remove, including useless catch clauses, concatenation, escape characters, useless comments, void operator and return statements.

No Useless catch Clauses

catch clauses that are useless just add extra bloat to our code and don’t do anything useful.

Therefore, we shouldn’t include them in our code. An example includes a catch clause that just throws the exception again:

try {
  foo();
} catch (e) {
  throw e;
}

Instead, we should do something in our catch clause to make it actually useful.

For instance, we may run something to handle the error before rethrowing the error as follows:

try {
  foo();
} catch (e) {
  handleError(e);
  throw e;
}

This way, the catch block actually does something rather than just rethrowing the error, which is redundant because we don’t need to wrap our code with try...catch just to rethrow an error.

The error will be thrown with or without it.

No Unnecessary Concatenation of Strings

It’s useless to concatenate 2 strings together as we do in the following code:

const foo = 'x' + 'y';

In the code above, both operands are strings. We only concatenate if we concatenate expressions like variables or anything else that aren’t constant strings.

We can simplify the example above to:

const foo = 'xy';

It’s much shorter and we don’t have a useless operator in between the 2 strings.

If we have some expression in between the strings or a string with an expression, then they’re also good use of the concatenation operator.

For instance, if we have:

const a = 1;
const foo = 'x' + a;

or:

const a = 1;
const foo = 'x' + a + 'y';

Then they’re good uses of the concatenation operator since we have a variable a within the chain of concatenations.

No Useless Escape Usage

Useless escape characters should be removed from our code.

Examples of this include strings or regex that only have escape characters in them like:

"'";
/!/;

Instead, we should either remove them or make them useful by including other characters in them:

"'foo";
/!foo/;

No Redundant return Statements

In a JavaScript function, a return; statement with nothing after it is useless.

For instance, if we have the following function:

const foo = () => {
  console.log('foo');
  return;
}

Then the return; statement is useless since the function would end with or without the return statement.

Instead, we should either remove it or make it do something like:

const foo = () => {
  return 'foo';
}

Don’t Use the void Operator

The void operator always return undefined . Therefore, it’s pretty useless in most cases in our JavaScript code.

In ES5, undefined values are mutable and the void operator lets us make sure that we get a real undefined value in our code. However, this use case is obsolete now.

Therefore, we shouldn’t use the void operator.

For instance, the following code is bad:

const foo = void 1;

We should just use undefined if we want to use it as follows:

const foo = undefined;

Remove Warning Comments

Warning comments shouldn’t be in production code. It indicates that the code is incomplete or has issues.

Therefore, the comments should be removed and the code should be fixed before going to production.

Examples of warning comments include:

// TODO: fix this
// FIXME: bad code

The comments above shouldn’t be in our code and the issues in the comments should be addressed.

Conclusion

Useless catch clauses are ones that just rethrow the error. We can do that without wrapping our code in try...catch , so it’s just useless code.

We should either remove the try...catch or put some code that handles the error in the catch block.

Redundant return statements are ones that don’t do anything at the end of the function.

They should either return something or be removed.

The void operator always returns undefined , so we should just use undefined and remove the void operator.

Finally, warning comments like todo and fix-me comments should be removed and the issues in the comments should be addressed.

Categories
JavaScript Best Practices

JavaScript Best Practices- Node.js Apps

JavaScript is a very forgiving language. It’s easy to write code that runs but has mistakes in it.

In this article, we’ll look at some mistakes that are easy to make in Node apps, including joining paths the wrong way, referencing process.env and more.

No String Concatenation with __dirname and __filename

In Node.js, __dirname and __filename are strings that have the current directory or current script file name respectively.

Many people try to concatenate them like the following to get the full path of a file:

const pathname = __dirname + '/bar.js'

However, the code above is probably since Node apps can be run on different platforms, which have different styles for paths.

For instance, Windows uses backslash a path segment separation, while Linux and macOS use forward slash as separators.

Therefore, concatenation isn’t a reliable way to join paths together. It’s very easy to create an invalid path by concatenation segments together.

Instead, we should use the path.join method to combine segments together as follows:

const path = require('path');
const pathname = path.join(__dirname, 'bar.js');

Then we won’t have to worry about the path structure of the path causing issues when running in different operating systems.

The path.resolve will get the fully qualified path with the full path separators. For instance, we can write:

const path = require('path');
const pathname = path.resolve(__dirname, 'bar.js');

and we get the full path generated by Node.js.

No Reference to process.env

The process.env object is used to store deployment or configuration settings. Putting it everywhere is a maintenance issue because we’re referencing this global dependency everywhere.

It can, therefore, lead to merge conflicts and deployment issues in multiserver setups.

The best practice for storing configuration settings is to store them all in one file and then reference that file instead.

For instance, instead of writing:

const dbPath = process.env.DB_PATH;

We should write:

const config = require("./config");
const dbPath = config.DB_PATH;

No process.exit()

process.exit() is use to immediately stop a Node process and exit the program.

This isn’t good because there may be lots of things running in the background that are needed by the program.

It can stop a progran complete when an error occurs.

For instance, if we have something like the following:

if (error) {
  process.exit(1);
}

Then the program exists immediatelt and there’s no chance for our program to respond to the error is error is truthy.

The better way to raise errors when they occur is to throw an exception. For instance, we can do that as follows:

if (error) {
  throw new Error("error");
}

This way, our program can catch the error and then respond to it. We’ll also see the full stack from the Error object from what’s been called before the error is thrown.

Don’t Use Synchronous Methods in Big Apps

Since Node is single-threade, running synchronous methods means that they have to finish before anything else can run. If it’s a long running process, then this is definitely a problem.

We don’t want one method to hold up our entire app. For instance, if we’re running a web server app, then we definitely shouldn’t have synchronous methods holding up the web server with one request.

However, for scripts that run line-by-line and don’t have multiple parts, synchronous methods are useful for simplifying the code and doing what we want sequentially without much thinking.

For instance, in a big production app, we shouldn’t be running methods like fs.readFileSync as follows:

const fs = require('fs');
const file = fs.readFileSync('foo.txt').toString();

We can instead use methods like readFile rom fs.promise to read a file:

const fs = require('fs').promises;
(async () => {
  const file = await fs.readFile('foo.txt')
  const content = file.toString();
  console.log(content);
})()

This way, we run asynchronous code in a sequence as we do with synchronous code without holding up the rest of our program.

Conclusion

If we want to create strings for file paths, we should use the path.join or path.resolve methods to create full paths.

This way, we won’t have to worry the path structure difference in different OSs.

The best way to store config settings is to put them all in a configuration and then reference everything from that file.

process.exit shouldn’t be used to exit the program when we encounter errors because it end the program abruptly, giving our program no chance of responding.

Finally, other than small scripts, we shouldn’t be using synchronous methods in our Node apps.