Another webdev blog

I'm Sébastien Castiel, and I talk about web development and some other stuff.

Writing modern JavaScript code

Remember when JavaScript was a language used to make elements change on the page when the cursor was over them? These days are over, every language evolves over time, and so does the way wa use them. Look at the code you wrote one or two years ago: do you feel ashamed? If yes, this post is for you 🙂

I'll try here to list some good practices to make your JavaScript code easier to write, read and maintain.

Use a linter that can format your code

The first advice I'm giving you is to use a linter that will check you respect some rules that make your code consistent from a file to another, especially if you're several developer to work on the same project: indentation, spaces in parenthesis, replace == by ===

But more important, make your linter fix automatically your code for you when possible. ESLint does that very well (with the --fix option), and it's well-integrated with all major IDEs to auto-fix files on save.

You can also use Prettier which is more focused on formatting than linting, but the result is basically the same 😉

Next point will help you chose what rules to use with your linter:

Use modern rules for your linter

If you wonder what rules to want for your code, here is a hint: StandardJS. It's a very strict linter that won't give you any choice in the rules, but each of them is more and more admitted by the community. Here are some examples:

StandardJS is a standalone Node module that can lint and fix your code, but if you want to use it in a big existing project and deactivate some rules (because some would need a lot of modifications), you can also use the ESLint predefined config. For instance I deactivated the rules no-mixed-operators and import/no-webpack-loader-syntax.

Use ES2015+ new features

If you develop with JavaScript there's no way you haven't heard of ES2015+ (or ES6, ES7…) features. Here are the ones I couldn't live without anymore:

function doSomething() {
  const a = doSomethingElse()
  const b = doSomethingWithA(a)
  const otherResults = { c: '😺', d: '🐶' }
  return { a, b, ...otherResults } // equivalent to { a: a, b: b }
}
const { a, c, ...rest } = doSomething() // Also works with arrays!
// `rest` looks like { b: ..., d: '🐶' }
// Please try to write the same code with classic promises ;)
async function doSomething() {
  const a = await getValueForA()
  const b = await getValueForBFromA(a)
  const [c, d] = await Promise.all([
    // parallel execution
    getValueForC(), getValueForDFromB(b)
  ])
  const total = await calculateTotal(a, b, c, d)
  return total / 1000
}

Wonder how to use these fantastic features? One of my articles gives you some advice! (By the way, with latest version of Node.js you probably won't need Babel anymore to use the greatest new features 😀)

Use functional programming

Very hype right now, functional programming is gaining a lot of success recently, not only in JavaScript. The reason? It makes the code more predictible, safer, deterministic, and a lot easier to maintain when you're used to it. Here are some simple advices:

First stop using for loops. In most (every?) case you don't need them. For instance:

const arr = [{ name: 'first', value: 13 }, { name: 'second', value: 7 }]

// Instead of:
const res = {}
for (let i = 0; i < arr.length; i++) {
  const calculatedValue = arr[i].value * 10
  if (calculatedValue > 100) {
    res[arr[i].name] = calculatedValue
  }
}

// Prefer:
const res = arr
  .map(elem => ({ name: elem.name, calculatedValue: elem.value * 10 }))
  .filter(elem => elem.calculatedValue > 100)
  .reduce((acc, elem) => ({
    [elem.name]: calculatedValue,
    ...acc
  }), {})

Okay I admit this is a very extreme example, and if you're not used to functional programming it may look more complicated. Maybe we can simplify it:

const enrichElementWithCalculatedValue =
  elem => ({ name: elem.name, calculatedValue: elem.value * 10 })
const filterElementsByValue = value =>
  elem => elem.calculatedValue > value
const aggregateElementInObject = (acc, elem) => ({
  [elem.name]: calculatedValue,
  ...acc
})
const res = arr
  .map(enrichElementWithCalculatedValue)
  .filter(filterElementsByValue(100))
  .reduce(aggregateElementInObject, {})

Here we defined three functions that basically exactly what their names say. Second advice: create local functions (even in existing functions) to document your code without comments!

Note that the three local functions don't modify context they're executed in. No external variable is modified, no other service is called… In functional programming they're called pure functions. They have some great advantages:

So my third advice: use pure functions a lot!

Some other advices to finish

Oh and to conclude, the greatest advice I can give you: always refactor your code! Making improvements to a module you wrote one year ago? Take the opportunity to replace var with const, to use arrow functions or async/await to simplify the code… It's always nicer to work on code you like 😉