Top-level await is available in Node.js modules
- Published at
- Updated at
- Reading time
- 2min
Node.js is a beautiful tool to write quick utility scripts. I use it in many of my build processes. Renaming files, downloading data, image processing โ Node.js scripts handle many tasks in my projects.
There has been one tiny annoyance, though. When dealing with asynchronous functionality such as making network requests, there was no top-level await support in Node.js (yet).
The await
keyword allows you to untangle Promises-based code, avoid chained then
calls and make source code more readable.
// promise-based code
Promise.resolve('hello world').then((asyncMsg) => {
console.log(msg);
});
// async/await code
const asyncMsg = await Promise.resolve('hello world');
console.log(msg);
Unfortunately, you could not use the await
keyword without wrapping it in an async
function.
// use an async IIFE
(async () => {
const asyncMsg = Promise.resolve('hello world');
console.log(asyncMsg);
})();
// use an async main function
async function main() {
const asyncMsg = Promise.resolve('hello world');
console.log(asyncMsg);
}
main();
And while wrapping code in an async function
is not terrible, its whole purpose is to enable the await
keyword. Is there a better way? Can we avoid these async
wrappers in Node.js code? Top-level await is now coming to the rescue!
Starting with Node.js v14
, top-level await is available (without the use of the --harmony-top-level-await
command line flag).
There's one catch: top-level await is only available in ES modules. There are three ways to make a Node.js script an EcmaScript module.
Be aware that if you're enabling ES modules in Node.js, you have to change all require
and module
statements with their import
and export
counterparts.
Use the mjs
file extension
Use the
file extension and call it a day! ๐
// File: index.mjs
//
// Command line usage: node index.mjs
const asyncMsg = await Promise.resolve('WORKS!');
console.log(asyncMsg); // "WORKS!"
Make the whole package a module
If you're developing a package you can also define the type
property in your package
to declare that it's based on ECMAscript modules.
// File: index.js
// (near package.json including { "type": "module" })
//
// Command line usage: node index.js
const asyncMsg = await Promise.resolve('WORKS!');
console.log(asyncMsg); // "WORKS!"
Define input-type
when evaluating string input
Sometimes you might need to pipe code into the Node.js binary or use the eval
flag. Use the input-type
flag to specify that the passed string value is an ECMAscript module.
node --input-type=module \
--eval="const asyncMsg = await Promise.resolve('WORKS!'); console.log(asyncMsg);"
It's beautiful that top-level await finally entered Node.js! I'll probably stick to the
file extension to use it in my scripts. Renaming a script file from js
to mjs
is quickly done and is not introducing significant changes.
But wait! Before going all in with top-level await, you might want to check how Node handles unsettled top-level promises.
Join 5.5k readers and learn something new every week with Web Weekly.