Node v14.8+: Top Level Async Await
Tags: javascript, node
Published: 2021-06-09 09:16:48

We all know what callback hell is, there is dedicated [page](http://callbackhell.com/) 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
```javascript
mkdir top-level-async
cd top-level-async
touch async-await.js
```

Copy Paste the below code.
```javascript
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();
```

#### 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:
```javascript
node async-await.js
```

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:

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

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

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](https://github.com/tc39/proposal-top-level-await)

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

let result = await fakeApiCall();
console.log(result);
```

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

Did we see any error?
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3f6iuotaqqxqo6s0yrbo.png)

huh? What happened?
Let's go back to this [link](https://github.com/tc39/proposal-top-level-await) 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules).

#### 1. Whole package as a `Module`
Do the following:
```javascript
npm init -y
touch async-await-package-module.js
```
This will generate a `package.json` as well.
Copy the below code and paste it in `async-await-package-module.js`
```javascript
function fakeApiCall() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("I am resolved, after 1000ms!!");
    }, 1000);
  });
}

let result = await fakeApiCall();
console.log(result);
```
>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:
```json
{
  "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"
}
```

Let's run in terminal:
```javascript
npm run dev
```

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
```javascript
touch async-await-mjs-file.mjs
```
Copy the code from above for which we got an error and paste it to `async-await-mjs-file.mjs`.

Run it in terminal:
```javascript
node async-await-mjs-file.mjs
```

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

Also V8's documentation does recommends this. Here is the [link](https://v8.dev/features/modules#mjs).

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

For example:
```javascript
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);'
```

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:
```javascript
touch async-await-live.mjs
```

Paste this code in `async-await-live.mjs`
```javascript
import axios from "axios";

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

Run in terminal like this:
```javascript
node async-await-live.mjs
```
Awesome, this should work as expected:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3yj22t0m6il70dm37xln.png)

Code used in this Post can be found [here](https://github.com/rojha-mdsol/devto-my-examples/tree/master/top-level-async)

### References used in this post:
* https://v8.dev/features/top-level-await
* https://github.com/tc39/proposal-top-level-await
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
* https://v8.dev/features/modules#mjs
* https://dmitripavlutin.com/ecmascript-modules-nodejs/

-- Thanks, Ravi