DEV Community

Stefan Judis
Stefan Judis

Posted on • Originally published at stefanjudis.com

TIL – Top-level await is available in Node.js modules

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);
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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!"
Enter fullscreen mode Exit fullscreen mode

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!"
Enter fullscreen mode Exit fullscreen mode

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);"
Enter fullscreen mode Exit fullscreen mode

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)