DEV Community

Ravi Ojha
Ravi Ojha

Posted on

Node v14.8+: Top Level Async Await

We all know what callback hell is, there is dedicated page for it. I recommend to go through it once. We all know how difficult it is to maintain a pyramid structure of code.

Then came promises which made our life much easy and code started looking much better, and much easy to maintain.

Then came async/await which made it even better.

In this post, let's see how to use top level async await.

But first, let us see how does it look now.

Setup

mkdir top-level-async
cd top-level-async
touch async-await.js
Enter fullscreen mode Exit fullscreen mode

Copy Paste the below code.

function fakeApiCall() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("I am resolved, after 1000ms!!");
    }, 1000);
  });
}

async function doSomething() {
  let result = await fakeApiCall();
  console.log(result);
}

doSomething();
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. fakeApiCall is function which returns a promise, call could also be reduced by any other client, for example axios (At the end of this post i will add a live example).
  2. doSomething is an async function, for now we can only use await in an async function.
  3. doSomething(); calling doSomething

Let's run the code in terminal:

node async-await.js
Enter fullscreen mode Exit fullscreen mode

In terminal you should be seeing something like this:
I am resolved, after 1000ms!!.

So, you see we can't use await at the top level or without being wrapped inside an async function.

But there is a workaround to use await without having the need to declare async function and without the need of calling them separately, calling them using IIFE:

function fakeApiCall() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("I am resolved, after 1000ms!!");
    }, 1000);
  });
}

(async function() {
  let result = await fakeApiCall();
  console.log(result);
})();
Enter fullscreen mode Exit fullscreen mode

Personally, I didn't see much progress from above, we still have to have anonymous function, although not the named one, and by the virtue of IIFE, it is being called immediately, although not manually.

Wrapping code in an async function is not a bad way to do it, It's just a way to enable the await keyword. But, Do we have any better to do this? The answer is YES, that's what we are discussing here.

With the latest node version(s) (v14.8+), we should be able to rewrite the above code to something like this. proposal-top-level-await

function fakeApiCall() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("I am resolved, after 1000ms!!");
    }, 1000);
  });
}

let result = await fakeApiCall();
console.log(result);
Enter fullscreen mode Exit fullscreen mode

Copy above code and paste it to async-await.js and re-run:

node async-await.js
Enter fullscreen mode Exit fullscreen mode

Did we see any error?
image

huh? What happened?
Let's go back to this link again. Let's read Synopsis one more time:

Top-level await enables modules to act as big async functions: With top-level await, ECMAScript Modules (ESM) can await resources, causing other modules who import them to wait before they start evaluating their body.

Now, the catch is top-level await is available only in ES modules. These are the three ways to make a Node.js script an ECMAScript module. Let's look at it one by one.

Here is a good place to get started with JS Modules . Link.

1. Whole package as a Module

Do the following:

npm init -y
touch async-await-package-module.js
Enter fullscreen mode Exit fullscreen mode

This will generate a package.json as well.
Copy the below code and paste it in async-await-package-module.js

function fakeApiCall() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("I am resolved, after 1000ms!!");
    }, 1000);
  });
}

let result = await fakeApiCall();
console.log(result);
Enter fullscreen mode Exit fullscreen mode

Note: This is exactly the same code as above, for which we got an error while running.

Update the package.json to look something like this:

{
  "name": "top-level-async",
  "version": "1.0.0",
  "description": "",
  "type": "module", // TODO: Add this
  "main": "async-await-package-module.js", //TODO: Update this
  "scripts": {
    "dev": "node async-await-package-module.js" //TODO: Update this
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
Enter fullscreen mode Exit fullscreen mode

Let's run in terminal:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Why do we have to run it like npm run dev? Can't we use node async-await-package-module.js?
The answer is YES.

So what does it mean?

Basically, by adding "type": "module", to your package, you have declared that this package is based on ECMAScript modules, and all .js files inside the folder containing this package.json (with type: module) will be executed as ECMAScript modules.

2. Using .mjs file extension

touch async-await-mjs-file.mjs
Enter fullscreen mode Exit fullscreen mode

Copy the code from above for which we got an error and paste it to async-await-mjs-file.mjs.

Run it in terminal:

node async-await-mjs-file.mjs
Enter fullscreen mode Exit fullscreen mode

Awesome, it works, we can see output as I am resolved, after 1000ms!!.

Also V8's documentation does recommends this. Here is the link.

3. Passing an argument --input-type=module and passing code as string to eval like --eval="<module_code>"

For example:

node --input-type=module --eval='function fakeApiCall() { return new Promise((resolve) => { setTimeout(() => { resolve("I am resolved, after 1000ms!!"); }, 1000); }); } let result = await fakeApiCall(); console.log(result);'
Enter fullscreen mode Exit fullscreen mode

The code that is added here --eval="code" is still the same one from above for which we got an error, its just in one line.

When we run it in terminal, it should work and give us:
I am resolved, after 1000ms!!

As promised, here is the live example using axios:

touch async-await-live.mjs
Enter fullscreen mode Exit fullscreen mode

Paste this code in async-await-live.mjs

import axios from "axios";

const response = await axios("https://randomuser.me/api");
console.log("****Got result***** \n", response.data);
Enter fullscreen mode Exit fullscreen mode

Run in terminal like this:

node async-await-live.mjs
Enter fullscreen mode Exit fullscreen mode

Awesome, this should work as expected:
image

Code used in this Post can be found here

References used in this post:

-- Thanks, Ravi

Top comments (0)