Top level await() without async() function in NodeJs
Here’s how async-await function typically works:
function myDummyBackendCall() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "Shivang",
id: "asmdhajkdhajdsh8qweuqoweuiqepoi-0q-0eueiuaisjdaKcjaisku",
});
});
});
}
// Used an async wrapper function to make an await call
(async () => {
let data = await myDummyBackendCall();
console.log(data);
})();
If you are a JS developer, you would know that without async
function, await
call will run into an error. And rightly so. The error will look something like this:
Let’s now talk about a different scenario
Here’s another example of my index.js file inside my NodeJs project wherein I haven’t enclosed my await
call inside an async
function (Node version that I am using is 19.3.0):
function myDummyBackendCall() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "Shivang",
id: "asmdhajkdhajdsh8qweuqoweuiqepoi-0q-0eueiuaisjdaKcjaisku",
});
});
});
}
//Note that there is no async wrapper function
let data = await myDummyBackendCall();
console.log(data);
// Output of node index.js
{
name: 'Shivang',
id: 'asmdhajkdhajdsh8qweuqoweuiqepoi-0q-0eueiuaisjdaKcjaisku'
}
You must be wondering how this is done and trust me, it’s fairly simple. All you have to do is to add a simple configuration, “type” with “module” as its value in the package.json file. Here’s an example:
{
"name": "src",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type":"module", // I'm here!
"scripts": {
"test": "echo \\"Error: no test specified\\" && exit 1"
},
"author": "",
"license": "ISC"
}
Behind the scenes
Within a package.json
, “type”
value of “module”
tells NodeJs to interpret the files with .js
extension as using ES module syntax, e.g.: import
statement or import()
expressions.
“type”
defines how NodeJs runtime should resolve specifiers or load modules.
There are two possible values for “type”
config:
- module
- commonjs (default)
There are ways to mix things up without adding type
to the package.json
file. Since we know that commonjs
is the default value applied to your NodeJs project, every file with .js
extension will be treated as CommonJS. But if you still want to load a file(s) as an ES Module, simply give the file .mjs
extension.
Here’s the sample code (without type
configuration):
{
"name": "src",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \\"Error: no test specified\\" && exit 1"
},
"author": "",
"license": "ISC"
}
Here’s the screenshot of the .mjs
file:
Here’s the code inside index.mjs
(exactly same code as above):
function myDummyBackendCall() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
name: "Shivang",
id: "asmdhajkdhajdsh8qweuqoweuiqepoi-0q-0eueiuaisjdaKcjaisku",
});
});
});
}
console.log(await myDummyBackendCall());
Here’s the result:
Here are some points straight from the official NodeJs documentation
- Files ending with
.mjs
are always loaded as ES modules regardless of the nearest parentpackage.json
. - Files ending with
.cjs
are always loaded as CommonJS regardless of the nearest parentpackage.json
.
But before you get overly optimistic…
This only works when await
call is made at the top level of modules. Here’s an illustration:
ECMAScript module loader is asynchronous. It means that top-level await enables the module to act like a BIG async function when NodeJs loads it as ES Module. Hence, the top-level await doesn’t need an explicit async function wrapper. It also means that an await without async is a myth!
Disclaimer
This is purely a NodeJs feature. There is no concept of .mjs
and .cjs
in vanilla JavaScript.
***
Note of thanks ❤️
Thank you for stopping by. Hope, you find this article helpful. Please follow me on medium and help me reach 1k followers ??. That will truly encourage me to put out more content.
P.S.: If you feel something can be improved or lacks proper explanation, drop me a note in the comment box or mail me at shiva.chaturvedi91@gmail.com. After all, you can teach me a thing or two as well.