Originally posted at michaelzanggl.com. Subscribe to my newsletter to never miss out on new content.
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
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.
Top comments (2)
It seems quite inconsistent to just use a semicolon in this specific case and no where else. In fact, this is a really good example of why you should use semicolons everywhere: to avoid strange errors caused by the implicitly inserted semicolons.
Do you have a reason why you don't use semicolons?
I really don't mind the use of semicolons, but personally haven't used them for some time now. It removes some clutter from the code and especially makes chains ([].map.filter etc.) easier to extend.
I am not fully convinced to use semicolons everywhere just for the edge case when a line starts with paranthesis or brackets, but perfectly understand when people do so. There are of course other good reasons to use semicolons.
But you can't switch ASI off, so anyways people have to learn about it. Because even with the use of semicolons you will run into things like this.
The above returns undefined because ASI places a semicolon after the
return
.Nowadays there are linters and tools like prettier that can really help with these problems.