468

I have been going over async/await and after going over several articles, I decided to test things myself. However, I can't seem to wrap my head around why this does not work:

async function main() {  
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = main();  
console.log('outside: ' + text);

The console outputs the following (node v8.6.0) :

> outside: [object Promise]

> inside: Hey there

Why does the log message inside the function execute afterwards? I thought the reason async/await was created was in order to perform synchronous execution using asynchronous tasks.

Is there a way could I use the value returned inside the function without using a .then() after main()?

Community
  • 1
  • 1
Felipe
  • 10,606
  • 5
  • 40
  • 57
  • 19
    No, only time machines can make asynchronous code synchronous. `await` is nothing but sugar for promise `then` syntax. – Bergi Oct 01 '17 at 18:59
  • Why does main return a value? If it should, probably it's not entry point and needs to be called by another function (e.g. async IIFE). – Estus Flask Oct 01 '17 at 19:12
  • 2
    @estus it was just a quick function name while I was testing things in node, not necessarily representative of a program's `main` – Felipe Oct 01 '17 at 23:11
  • 2
    FYI, `async/await` is part of ES2017, not ES7 (ES2016) – Felix Kling Oct 02 '17 at 17:48
  • 1
    For the interactive node shell (REPL), try `node --experimental-repl-await`. – nomad Oct 07 '20 at 18:37
  • I'm using Next.js, and this was helpful for me: https://stackoverflow.com/a/68339259/470749 – Ryan Nov 17 '21 at 01:06
  • @nomad For recent versions of node that’s no longer necessary. The repl supports top level await by default – slebetman Jul 18 '23 at 13:27

15 Answers15

552

I can't seem to wrap my head around why this does not work.

Because main returns a promise; all async functions do.

At the top level, you must either:

  1. Use top-level await (proposal, MDN; ES2022, broadly supported in modern environments) that allows top-level use of await in a module.

    or

  2. Use a top-level async function that never rejects (unless you want "unhandled rejection" errors).

    or

  3. Use then and catch.

#1 top-level await in a module

You can use await at the top-level of a module. Your module won't finish loading until the promise you await settles (meaning any module waiting for your module to load won't finish loading until the promise settles). If the promise is rejected, your module will fail to load. Typically, top-level await is used in situations where your module won't be able to do its work until the promise is settled and won't be able to do it at all unless the promise is fulfilled, so that's fine:

const text = await main();
console.log(text);

If your module can continue to work even if the promise is rejected, you could wrap the top-level await in a try/catch:

// In a module, once the top-level `await` proposal lands
try {
    const text = await main();
    console.log(text);
} catch (e) {
    // Deal with the fact the chain failed
}
// `text` is not available here

when a module using top-level await is evaluated, it returns a promise to the module loader (like an async function does), which waits until that promise is settled before evaluating the bodies of any modules that depend on it.

You can't use await at the top level of a non-module script, only in modules.

#2 - Top-level async function that never rejects

(async () => {
    try {
        const text = await main();
        console.log(text);
    } catch (e) {
        // Deal with the fact the chain failed
    }
    // `text` is not available here
})();
// `text` is not available here, either, and code here is reached before the promise settles
// and before the code after `await` in the main function above runs

Notice the catch; you must handle promise rejections / async exceptions, since nothing else is going to; you have no caller to pass them on to (unlike with #1 above, where your "caller" is the module loader). If you prefer, you could do that on the result of calling it via the catch function (rather than try/catch syntax):

(async () => {
    const text = await main();
    console.log(text);
})().catch(e => {
    // Deal with the fact the chain failed
});
// `text` is not available here, and code here is reached before the promise settles
// and before the code after `await` in the main function above runs

...which is a bit more concise, though it somewhat mixes models (async/await and explicit promise callbacks), which I'd normally otherwise advise not to.

Or, of course, don't handle errors and just allow the "unhandled rejection" error.

#3 - then and catch

main()
    .then(text => {
        console.log(text);
    })
    .catch(err => {
        // Deal with the fact the chain failed
    });
// `text` is not available here, and code here is reached before the promise settles
// and the handlers above run

The catch handler will be called if errors occur in the chain or in your then handler. (Be sure your catch handler doesn't throw errors, as nothing is registered to handle them.)

Or both arguments to then:

main().then(
    text => {
        console.log(text);
    },
    err => {
        // Deal with the fact the chain failed
    }
);
// `text` is not available here, and code here is reached before the promise settles
// and the handlers above run

Again notice we're registering a rejection handler. But in this form, be sure that neither of your then callbacks throws any errors, since nothing is registered to handle them.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thinking of it as a promise explains now why the function returns immediately. I experimented with making a top-level anonymous async function and I get results that make sense now – Felipe Oct 01 '17 at 23:10
  • 4
    @Felipe: Yes, `async`/`await` are syntactic sugar around promises (the good kind of sugar :-) ). You're not just *thinking* of it as returning a promise; it actually does. ([Details](https://tc39.github.io/ecma262/#sec-async-function-definitions-EvaluateBody).) – T.J. Crowder Oct 02 '17 at 06:48
  • I don't think you should mix async and old `Promise.catch()`, it makes it harder to read the code. If you use async, you should also use regular try/catch. – undefined Apr 30 '19 at 05:17
  • 1
    @LukeMcGregor - I showed both above, with the all-`async` option first. For the top-level function, I can see it either way (mostly because of two levels of indentation on the `async` version). – T.J. Crowder Apr 30 '19 at 07:01
  • 3
    @Felipe - I've updated the answer now that the top-level `await` proposal has reached Stage 3. :-) – T.J. Crowder Jun 18 '19 at 08:27
  • If you just want the error to be dumped to the console like in a normal `function`, then just put `console.error(e.stack)` in your `catch` block. – Sam Jun 25 '19 at 03:44
  • Top-level await is now stage 4. Not sure how wide spread support is yet as this is brand new syntax. Mainly thinking of older browser versions which tend to hang around a while. Webpack 5 knows about this syntax as does babel. – wheredidthatnamecomefrom May 28 '22 at 17:13
  • @wheredidthatnamecomefrom - Yep, that's why I edited the answer on March 9th to say ES2022 (the next snapshot spec). :-) It's universally supported in modern browsers and in Node.js; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#browser_compatibility. – T.J. Crowder May 28 '22 at 17:45
46

2023 answer: you can now use top level await in all supported versions of node.js

Most of the answers above are a little out of date or very verbose, so here's a quick example for node 14 onwards.

Make a file called runme.mjs:

import * as util from "util";
import { exec as lameExec } from "child_process";
const exec = util.promisify(lameExec);
const log = console.log.bind(console);

// Top level await works now
const { stdout, stderr } = await exec("ls -la");
log("Output:\n", stdout);
log("\n\nErrors:\n", stderr);

Run node runme.mjs

Output:
 total 20
drwxr-xr-x  2 mike mike 4096 Aug 12 12:05 .
drwxr-xr-x 30 mike mike 4096 Aug 12 11:05 ..
-rw-r--r--  1 mike mike  130 Aug 12 12:01 file.json
-rw-r--r--  1 mike mike  770 Aug 12 12:12 runme.mjs



Errors:

You don't need to set type: module in your package.json because .mjs files are assumed to be ESM.

In TypeScript using esrun

In TypeScript, using esrun (which is WAY better than ts-node)

npm i @digitak/esrun

Run just run the TS with npx esrun deletemetoo.ts:

Output:
 total 128
drwxr-xr-x@  8 mikemaccana  staff    256 18 Jul 13:50 .
drwxr-xr-x@ 13 mikemaccana  staff    416 17 Jul 14:05 ..
-rw-r--r--@  1 mikemaccana  staff     40 18 Jul 13:27 deleteme.ts
-rw-r--r--@  1 mikemaccana  staff    293 18 Jul 13:51 deletemetoo.ts
drwxr-xr-x@ 85 mikemaccana  staff   2720 18 Jul 13:51 node_modules
-rw-r--r--@  1 mikemaccana  staff  46952 18 Jul 13:51 package-lock.json
-rw-r--r--@  1 mikemaccana  staff    503 18 Jul 13:51 package.json
-rw-r--r--@  1 mikemaccana  staff   1788 18 Jul 13:48 utils.ts

You don't need to set type: module in your package.json because esrun has smart defaults.

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
  • 2
    I'm using Next.js, and this was helpful for me: https://stackoverflow.com/a/68339259/470749 – Ryan Nov 17 '21 at 01:06
  • it doesn't work with things like this `console.log(await 'bar');` which is no bueno – Alexander Mills Dec 24 '22 at 03:17
  • 3
    make sure to use `"type": "module"` in your `package.json`. – Ryan Norooz Jan 25 '23 at 08:58
  • 1
    @RyanNorooz it will worth without `"type": "module"` in `package.json` – mikemaccana Jan 25 '23 at 13:57
  • @AlexanderMills Yes it does. Try `console.log(await exec("ls -la"));` in the example above. – mikemaccana Jan 25 '23 at 13:59
  • @RyanNorooz Yes, `*.mjs` has been in this answer, and mentioned twice, for the last five hundred and thirty one days. See https://stackoverflow.com/a/68756418/123671 – mikemaccana Jan 25 '23 at 22:17
  • this answer is a bit misleading, top-level `await` only works if you are using *ES modules* (ie, `"type": "module"` on `package.json`), instead of *CommonJS* as explained [here](https://www.typescriptlang.org/docs/handbook/esm-node.html) – Bersan Jul 17 '23 at 22:16
  • @Bersan that's not correct, you don't need `type: module` in `package.json`. All `.mjs` files are assumed to be in ESM. See your link: https://www.typescriptlang.org/docs/handbook/esm-node.html#new-file-extensions Try it. – mikemaccana Jul 18 '23 at 12:54
  • 1
    @mikemaccana I see. With the `.mts` extension it will assume always to be ESModule (for the typescript case) – Bersan Jul 19 '23 at 17:25
  • @Bersan Indeed! However tools like `esrun` will happily run `.ts` files as ESM without requiring `package.json` to be modified. – mikemaccana Jul 20 '23 at 13:29
35

Top-Level await has moved to stage 3 stage 4 (see namo's comment), so the answer to your question How can I use async/await at the top level? is to just use await:

const text = await Promise.resolve('Hey there');
console.log('outside: ' + text)

Of if you want a main() function: add await to the call to main() :

async function main() {
    var value = await Promise.resolve('Hey there');
    console.log('inside: ' + value);
    return value;
}

var text = await main();  
console.log('outside: ' + text)

Compatibility

Taro
  • 1,432
  • 1
  • 16
  • 15
15

To give some further info on top of current answers:

The contents of a node.js file are currently concatenated, in a string-like way, to form a function body.

For example if you have a file test.js:

// Amazing test file!
console.log('Test!');

Then node.js will secretly concatenate a function that looks like:

function(require, __dirname, ... perhaps more top-level properties) {
  // Amazing test file!
  console.log('Test!');
}

The major thing to note, is that the resulting function is NOT an async function. So you cannot use the term await directly inside of it!

But say you need to work with promises in this file, then there are two possible methods:

  1. Don't use await directly inside the function
  2. Don't use await at all

Option 1 requires us to create a new scope (and this scope can be async, because we have control over it):

// Amazing test file!
// Create a new async function (a new scope) and immediately call it!
(async () => {
  await new Promise(...);
  console.log('Test!');
})();

Option 2 requires us to use the object-oriented promise API (the less pretty but equally functional paradigm of working with promises)

// Amazing test file!
// Create some sort of promise...
let myPromise = new Promise(...);

// Now use the object-oriented API
myPromise.then(() => console.log('Test!'));

It would be interesting to see node add support for top-level await!

Gershom Maes
  • 7,358
  • 2
  • 35
  • 55
  • 3
    [Node added support for top-level await behind a flag in v13.3]https://stackoverflow.com/questions/59585793/top-level-await-doesnt-work-in-the-latest-node-js/59585976#59585976). – Dan Dascalescu Feb 08 '21 at 05:44
9

You can now use top level await in Node v13.3.0

import axios from "axios";

const { data } = await axios.get("https://api.namefake.com/");
console.log(data);

run it with --harmony-top-level-await flag

node --harmony-top-level-await index.js

wobsoriano
  • 12,348
  • 24
  • 92
  • 162
  • 1
    That release changelog doesn't mention anything about top-level await, and it seems [support for the flag started with v13.3](https://stackoverflow.com/questions/59585793/top-level-await-doesnt-work-in-the-latest-node-js/59585976#59585976). – Dan Dascalescu Feb 08 '21 at 05:44
  • with node version 18/19, I get `node: bad option: --harmony-top-level-await`, and also top level await doesn't work, very confusing, I thought this was pretty much a guaranteed new feature – Alexander Mills Dec 24 '22 at 03:15
6

The actual solution to this problem is to approach it differently.

Probably your goal is some sort of initialization which typically happens at the top level of an application.

The solution is to ensure that there is only ever one single JavaScript statement at the top level of your application. If you have only one statement at the top of your application, then you are free to use async/await at every other point everwhere (subject of course to normal syntax rules)

Put another way, wrap your entire top level in a function so that it is no longer the top level and that solves the question of how to run async/await at the top level of an application - you don't.

This is what the top level of your application should look like:

import {application} from './server'

application();
Duke Dougal
  • 24,359
  • 31
  • 91
  • 123
  • 1
    You are correct that my goal is initialization. Things such as database connections, data pulls etc. In some cases it was necessary to get a user's data before proceeding with the rest of the application. Essentially you are proposing that `application()` be async? – Felipe Aug 08 '18 at 15:41
  • 1
    No, I'm just saying that if there is only one JavaScript statement at the root of your application then your issue is gone - the top level statement as shown is not async. The problem is that it is not possible to use async at the top level - you can't get await to actually await at that level - therefore if there is only ever one statement at the top level then you have sidestepped that issue. Your initialization async code is now down in some imported code and therefore async will work just fine, and you can initialize everything at the start of your application. – Duke Dougal Aug 08 '18 at 22:40
  • 1
    CORRECTION - application IS an async function. – Duke Dougal Aug 08 '18 at 22:47
  • 6
    I'm not being clear sorry. The point is that ordinarily, at the top level, an async function does not await.... JavaScript goes straight on to the next statement so you cannot be certain that your init code has completed. If there is only one single statement at the top of your application that that just does not matter. – Duke Dougal Aug 08 '18 at 22:51
5

For Browser you need to add type="module"

without type="module"

<script>
  const resp = await fetch('https://jsonplaceholder.typicode.com/users');
  const users = await resp.json();
  console.log(users)
</script>

with type="module"

<!--script type="module" src="await.js" -->
<script type="module">
  const resp = await fetch('https://jsonplaceholder.typicode.com/users');
  const users = await resp.json();
  console.log(users)
</script>
uingtea
  • 6,002
  • 2
  • 26
  • 40
  • To the point answer! I looked for a script that is "included" in html and not in a random JS file being imported. – codepleb May 26 '23 at 15:25
4

i like this clever syntax to do async work from an entrypoint

void async function main() {
  await doSomeWork()
  await doMoreWork()
}()
ChaseMoskal
  • 7,151
  • 5
  • 37
  • 50
  • Pretty syntax! I've never seen void used like that. If anyone's curious this explains why it works: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void. Just throwing this out there: if you're just using a single async function or you don't care about the order multiple async functions execute in your can also use `void doSomeWork()`. Void essentially throws any returned value away and instead returns `undefined` instead – Themis Oct 10 '22 at 18:35
  • For just a simple test typescript project, this really helps since I don't have to make much config works in `tsconfig.json`. Thanks! – HoangLM Nov 25 '22 at 07:53
4

Other solutions were lacking some important details for POSIX compliance:

You need to ...

  • Report a 0 exit status on success and non-zero on fail.
  • Emit errors to stderr output stream.
#!/usr/bin/env node

async function main() {
 // ... await stuff ... 
}

// POSIX compliant apps should report an exit status
main()
    .then(() => {
        process.exit(0);
    })
    .catch(err => {
        console.error(err); // Writes to stderr
        process.exit(1);
    });

If you're using a command line parser like commander, you may not need a main().

Example:

#!/usr/bin/env node

import commander from 'commander'

const program = new commander.Command();

program
  .version("0.0.1")
  .command("some-cmd")
  .arguments("<my-arg1>")
  .action(async (arg1: string) => {
    // run some async action
  });

program.parseAsync(process.argv)
  .then(() => {
    process.exit(0)
  })
  .catch(err => {
    console.error(err.message || err);
    if (err.stack) console.error(err.stack);
    process.exit(1);
  });
Tony O'Hagan
  • 21,638
  • 3
  • 67
  • 78
3

Node -
You can run node --experimental-repl-await while in the REPL. I'm not so sure about scripting.

Deno -
Deno already has it built in.

nomad
  • 471
  • 6
  • 12
2

Now with ECMAScript22, we can use await at the top-level module.

This is an example with ( await top-level ):

const response = await fetch("...");
console.log(response):

an other example without (await top-level )

  async function callApi() {
    const response = await fetch("...");
    console.log(response)      
}
callApi()
nassim miled
  • 581
  • 1
  • 6
  • 18
1
  1. You need to add type in package.json

    "type": "module"
    
  2. You are good to go.

    import axios from 'axios';
    const res = await axios.get('https://api.github.com/users/wesbos');
    console.log(res.data);
    

Remember if you change type of document then you must have to write code in ES6 way.

Ali Raza
  • 1,026
  • 13
  • 20
1

If your only goal is to control the execution order of asynchronous code mixed with other code for testing purposes, you could wrap the entire top-level code inside of an immediately-invoked function expression (IIFE) defined as an async function. In the example from the question, you would then add await before calling main().

You can use this pattern when your code is not already in an async function or at the top level body of a module. In other words, if you're just testing a bunch of code inside of a js file and using tools like Live Server, RunJs, or any other type of JavaScript playground to watch the console window, wrap all of your code in an IIFE defined as async and use the await keyword when you want to wait for asynchronous code to finish before executing the next line.

let topLevelIIFE = (async () => {  
  async function main() {  
      var value = await Promise.resolve('Hey there');
      console.log('inside: ' + value);
      return value;
  }

  var text = await main();  
  console.log('outside: ' + text);
  
})()

You would not need to use this pattern when running the code specified in the body of the IIFE inside of the REPL in Chrome DevTools or another browser REPL tool that behaves similarly.

urosc
  • 1,938
  • 3
  • 24
  • 33
0

In NodeJS 14.8+, you can use top-level await module (#3 solution). You can rename also .js to .mjs (ES module) instead of .js (.cjs CommonJS).

Domus71
  • 151
  • 1
  • 5
-7

Since main() runs asynchronously it returns a promise. You have to get the result in then() method. And because then() returns promise too, you have to call process.exit() to end the program.

main()
   .then(
      (text) => { console.log('outside: ' + text) },
      (err)  => { console.log(err) }
   )
   .then(() => { process.exit() } )
Peracek
  • 1,801
  • 1
  • 15
  • 17
  • 4
    Wrong. Once all promises have been accepted or rejected and no more code is running in the main thread, the process terminates by itself. –  May 29 '18 at 21:31
  • @Dev: normally you would like to pass different values to `exit()` to signal whether an error happened. – 9000 May 09 '19 at 16:14
  • 1
    @9000 Yes, but that is not being done here, and since an exit code of 0 is the default there's no need to include it –  May 11 '19 at 01:45
  • @9000 in fact, the error handler should probably be using `process.exit(1)` –  May 15 '19 at 09:20