Three JavaScript gotchas in practice

JavaScript has many subtleties as new developers quickly realize. Although most of the these behaviours they don't seem to have any practical benefits, this is not necessarily the case. In this article I will discuss three real world scenarios, which are easy to forget and can lead to nasty bugs.

I personally don’t like interview tests. They tend to dig deep in the theory of the language at a near-academic level and a fair bit of them are not used in real life.

On the other hand some of these concepts are worth mentioning. Let’s have a look at three of them.

Promises need to be handled

Promises have become part of every developer’s life. They represent an asynchronous event which will eventually (sometime in the future) occur.

As being asynchronous Promises let other parts of the code execute, and once the result (either the data we want or the error that something went wrong) arrives, the code attached to the promise execute.

If we don’t manage asynchronous code, it can cause issues. A few years ago callbacks were supposed to do this job. Luckily, we don’t have to work with callbacks if we don’t want to, but the new generation of async tools still needs to be managed.

Besides our custom created promises many frequently used methods return a Promise. async functions and the popular request-promise are just two examples.

Anything that returns a promise needs to be properly handled. One should carefully read the documentation or have a look at the source code to see if a method returns a Promise.

How can we handle them?

When functions returning a promise are called, we can simply await them. This, of course, assumes that they are wrapped inside an async function.

Consider the following request:

const rp = require('request-promise-native');

const getDataFromAPI = () => {
  const options = {
    uri: 'https://api.somewhere.com',
    method: 'GET',
    json: true
  };

  return rp(options);
};

Somewhere else we can do something like this:

const handleData = async () => {
  try {
    const data = await getDataFromAPI();
    // .. more code
  } catch (error) {
    // .. error handling if something has come up while fetching data
  }
};

await basically stops the flow and waits for the promise to get either resolved or rejected.

Using async/await is a no brainer for new projects. As of writing this the long term supported version of Node.js is 8.11 where the async/await syntax is supported.

If version 6 needs to be used for any reason, the then and catch or other promise libraries can be used to handle promises.

The above example is very simple but it’s easy to get lost as the applications scales. Node.js doesn’t always complain if the promises are not handled, so we need to ensure that this step is not skipped.

Convert to lower case

Exploring other areas of JavaScript I want to briefly mention a scenario that can lead to sometimes hard-to-debug errors.

Say that we have to check if a string matches another string. Examples can be user inputs or configuration settings. When these strings need to be matched to perform an action (or another action is performed if they don’t match), it’s a good idea to convert both strings to lower cases.

Consider the following dummy example:

const config = {
  headingTitle: 'Read Harry Potter books'
};

const displayContent = () => {
  const title = 'Read harry Potter books';

  if (title === config.headingTitle) {
    console.log('Titles are matching!');
  }
};

If we call displayContent(), nothing will be displayed in the console because of a small typo in title inside displayContent.

Mistakes like this regularly occur and they are often hard to debug. You sit there for hours just to eventually find out that some bad capitalization broke the code. This can be extremely annoying, so it’s better to be prepared and try to prevent it from happening.

A good way to eliminate these errors is to apply toLowerCase() on both strings:

if (title.toLowerCase() === config.headingTitle.toLowerCase()) {
  console.log('Titles are matching!');
}

Now the text will properly show in the console.

Checking for empty object or array

Our code often needs to check if an input object or array is empty. If so, an error can be thrown or a different logic is applied compared to the scenarios when the object or the array has the required properties or values.

Because objects and arrays are compared by reference, checking for an empty object or array will lead to code which will never run:

const obj = {};

if (obj === {}) {
  console.log('This will never run.');
}

and

const arr = [];

if (arr === []) {
  console.log('This will never run.');
}

What can we do then?

As for objects, we can check if the object has properties using the Object.keys method:

const obj = {};

if (Object.keys(obj).length === 0) {
  console.log('This will run!');
}

Object.keys returns an array whose values are the keys of the object. If the object is empty, it won’t be any keys, therefore the length of the array returned will be zero.

Similarly, this code will run:

const arr = [];

if (arr.length === []) {
  console.log('This will surely run!');
}

This one is easier and depicts a more common scenario. If the array is empty, its length will be zero.

Conclusion

The scenarios above are common causes of bugs in the code and they are often hard to backtrack. Keeping some tricks like these in mind a small part of the precious developer’s time can be spent on other challenges.

I know that it’s not always easy to remember everything but practices like these will be part of the daily routine over time.

Thanks for reading and see you next time.