Reputation: 11959
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()
?
Upvotes: 537
Views: 460072
Reputation: 123058
Most of the answers above are a little out of date or very verbose, so here's a quick example for node 14 onwards.
There's a separate TypeScript answer below.
Make a file called runme.mjs
:
import { exec as oldExec } from "node:child_process";
import { promisify } from "node:util";
const exec = promisify(oldExec);
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.
esrun
In TypeScript, using esrun
.
esrun
is better than ts-node
, tsrun
and tsx
because:
npm i esrun
Run just run the TS with npx esrun file.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 yourpackage.json
becauseesrun
has smart defaults.
Upvotes: 78
Reputation: 8131
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:
await
directly inside the functionawait
at allOption 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!');
})()
.catch(err => console.log('Fatal error', err);
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
!
Upvotes: 17
Reputation: 22682
Other solutions were lacking some important details for POSIX compliance:
You need to ...
stderr
output stream.This will ensure that your command line app correctly integrates with other command line tools in bash/zsh etc and even Windows powershell or cmd. For example, if you're using it as part of a sequence of apps connected via a pipeline shell operator |
then writing errors to stderr
ensures that errors are visible (not piped into the next command). Only stdout
should be streamed as stdin
into the next command in your pipeline. Returning a POSIX compliant exit status means you can correctly chain your Node app with other commands using &&
or ||
shell operators.
#!/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);
});
Upvotes: 6
Reputation: 1542
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)
--harmony-top-level-await
Upvotes: 38
Reputation: 21
const resp = await fetch('https://jsonplaceholder.typicode.com/users');
const users = await resp.json();
console.log(users)
Upvotes: 2
Reputation: 21
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.
Upvotes: 1
Reputation: 6524
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>
Upvotes: 6
Reputation: 1074028
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:
Use top-level await
(proposal, MDN; ES2022, broadly supported in modern environments) that allows top-level use of await
in a module.
or
Use a top-level async
function that never rejects (unless you want "unhandled rejection" errors).
or
Use then
and catch
.
await
in a moduleYou 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.
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.
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.
Upvotes: 600
Reputation: 619
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()
Upvotes: 2
Reputation: 1063
You need to add type in package.json
"type": "module"
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.
Upvotes: 1
Reputation: 161
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).
Upvotes: 0
Reputation: 13434
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
Upvotes: 9
Reputation: 7651
i like this clever syntax to do async work from an entrypoint
void async function main() {
await doSomeWork()
await doMoreWork()
}()
Upvotes: 6
Reputation: 541
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.
Upvotes: 3
Reputation: 26326
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();
Upvotes: 6
Reputation: 1971
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() } )
Upvotes: -7