IIFEs in JavaScript and how to avoid this common mistake

Published 5/19/2019

Since JavaScript doesn't support top level await quite yet, the typical node index file might look something like this

const http = require('http')

(async () => {
  // await ...
})()

We require the http library and then have an immediately invoked function expression(IIFE) just so we can use async await.

With IIFEs we write functions and immediately execute them. This is so everything that is happening within the function stays within the function and is not accessible from outside of it. It is also the way to use await at the top level as of now.

Problem

I am sure many of you have fallen into this trap, as the above code actually breaks.

Uncaught TypeError: require(...) is not a function

The reason it crashes is because JavaScript tries to execute this (try formatting the above code in your editor to get the same result)

const http = require('http')(async () => {
  // ...
})()

It expects the require method to return a function, in which we pass an asynchronous function and then we execute the result of that. 🀯

The error becomes especially hard to catch when you have two IIFEs in a row.

Uncaught TypeError: (intermediate value)(...) is not a function

Check out my e-book!

Learn to simplify day-to-day code and the balance between over- and under-engineering.

Common workarounds

There are common workarounds for this, which are all about telling JavaScript that the IIFE is indeed a new statement, most notably

const http = require('http')

void (async () => { // < note the void at the beginning

})()

or

const http = require('http'); // < note the semicolon

(async () => {

})()

or even

const http = require('http')

!(async () => { // < note the exclamation mark

})()

Labels

The above workarounds are nothing new, but here is something you might have not seen yet.

const http = require('http')

IIFE: (async () => {

})()

Yup, labels work as well. You can put labels before any statement. We can replace IIFE with anything we want at this point as long as it follows the syntax. If it works as a variable name, it works as a label identifier.

一か八か: 1 + 1

Labels are actually quite interesting. Look at the following code snippet taken from MDN.

foo: {
  console.log('this will be executed');
  break foo;
  console.log('this will not be executed');
}
console.log('this will be executed as well');

Conclusion

Since labels are not so well known, it is probably better to stick with semicolons or void, but it is nonetheless interesting. I like how they add some documentation to IIFEs. Well, let's just wait a little more for top level await.