Add a Pre-Commit Git Hook to a Node.js Repo with Husky

We can easily add a pre-commit hook to a NodeJS repository using the Husky package. The example below shows how this works.

First install Husky:

$ npm install husky

Now add a file .huskyrc in the root of your repo with the JSON below:

{
  "hooks": {
    "pre-commit": "echo 'commit event detected'"
  }
}

We can test this out to prove it is working:

$ git add .
$ git commit -m "hook testing" 
husky > pre-commit (node v11.6.0)
commit event detected

The hook executed but the commit succeeds.

To demonstrate how the hook can fail and prevent commits, we can set the exit status code of the command:

{
  "hooks": {
    "pre-commit": "echo 'commit event detected'; exit 1"
  }
}

Now we will get the following result:

$ git commit -m "hook test"
husky > pre-commit (node v11.6.0)
commit event detected
husky > pre-commit hook failed (add --no-verify to bypass)

This shows that commits will not be allowed if the exit status code of the command in the pre-commit hook is 1 (error).

Naturally we want to do something useful with the pre-commit hook, e.g. making sure all unit tests pass: a command like Jest will set the exit status to 1 (error) if there failing tests. Assuming we have defined npm test with a command like Jest or similar we could set our hook to:

{
  "hooks": {
    "pre-commit": "npm test"
  }
}

Now committing code which fails unit tests will be impossible, unless we explicitly bypass the hook like this:

$ git commit -m "hook test" --no-verify

References

https://www.npmjs.com/package/husky

JavaScript ES6 const Keyword Allows Changes

The ES6 JavaScript const keyword prevents re-assignment of a reference, but it does not actually prevent data modification of any object being referenced.

Of course, a primitive value declared const cannot be changed:

> const pi = 3.1415926
> pi = 3
TypeError: Assignment to constant variable.

However, objects declared with const can still have their properties changed:

const myObject = {
  a: 5, 
  b: 10, 
  c: 15
} 

myObject.b = -1 // no error

console.dir(myObject)

Result:

{ a: 5, b: -1, c: 15 }

Similarly, arrays declared with const in ES6 can be edited! They simply cannot be re-assigned.

const arr = [1, 2, 3, 4, 5]

a[1] = -1 // this is fine

console.dir(arr)

Result:

[1, -1, 3, 4, 5]

Using methods to manipulate the const array is still allowed:

const arr = [1, 2, 3, 4, 5] 

arr.pop() // allowed

console.dir(arr)

Result:

[1, 2, 3, 4]

The following use of the delete operator is ok with const as well:

const arr = [10, 20, 30]

delete arr[1] // works

console.dir(arr)

Result:

[10, <1 empty item>, 30]

Note that delete leaves gaps in the array; for objects it will leave null.

Countless more examples are possible but the key point is: arrays and objects being referenced by a const variable can still be mutated.

Promise.all with async/await in JavaScript

Promise.all can be used to await an array of promises, all of which can resolve at any time individually. Imagine launching multiple simultaneous requests to many APIs for example, all with varying unpredictable response times.

Promise.all will only resolve once all of the promises in the input array have resolved.

The following example shows two functions which return in 1 and 2 seconds respectively. Note that we are using the sleep function from npm here for maximum simplicity. This can be installed with: npm install sleep.

const sleep = require('sleep')

const f = async () => {
  sleep.sleep(1) // 1 second
  console.log('f sleep done, returning...')
  return 'f result'
}

const g = async () => {
  sleep.sleep(2) // 2 seconds
  console.log('g sleep done, returning...')
  return 'g result'
}

(async () => {
  const promise1 = f()
  const promise2 = g()

  const allResults = await Promise.all( [promise1, promise2] )

  console.log('All results: ' + JSON.stringify(allResults))
})()

Note the IIFE because we cannot have a top-level async function using the async/await syntax.

If we don’t need the return values, we can simply do:

await Promise.all( [promise1, promise2] )

To capture only some of the results, we can use destructuring assignment:

const [ result1 ] = await Promise.all( [promise1, promise2] )

The combination of Promise.all and async/await provides a very readable and compact way to make many asynchronous calls and use the results.