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).
await
allows you to untangle Promises-based code and make it 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 this wrapping 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 to the rescue!
top-level await
is available "unflagged" in Node.js since v14.8
Starting with Node.js v14.8
, 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.
Use the mjs
file extension
Use the .mjs
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.json
.
// 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 ES module in these situations.
node --input-type=module \
--eval="const asyncMsg = await Promise.resolve('WORKS!'); console.log(asyncMsg);"
Await, await, await...
This functionality is beautiful! I'll probably stick to the .mjs
file extension for my scripts. Renaming a script file from js
to mjs
is quickly done and is not introducing significant changes.
If you like these quick tips, I send out a weekly newsletter.
Top comments (0)