How can I use async/await at the top level?
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 ofawait
in a module.or
Use a top-level
async
function that never rejects (unless you want "unhandled rejection" errors).or
Use
then
andcatch
.
#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.
Because I can't run await on the top level, I have to put it into an async function - why can I then call that async function directly?
Top-level await used to not be a thing, but it is possible now in ES6 modules.
One reason why top-level await used to not be a thing, and is still not a thing outside of modules is that it could permit syntactical ambiguity. Async and await are valid variable names. outside of modules. If a non-module script permitted top-level await, then, short of re-working the specification (and breaking backwards compatibility), there would be circumstances when the parser couldn't determine whether a particular instance of await
was a variable name, or was used as the syntax to wait for the Promise on its right-hand side to resolve.
To avoid any possibility of ambiguity, the parser, when parsing a section of code, essentially needs to have flags that indicate whether await
is valid as an identifier at any given point, or whether it's valid as async syntax, and those two must never intersect.
Module scrips permit top-level await (now) because the use of await as an identifier has always been forbidden in them, so there is no syntactical ambiguity.
In contrast, there are zero issues with using .then
on the top level because it doesn't result in any ambiguity in any circumstances.
Why doesn't it just return a Promise which is never executed because it doesn't get awaited?
Promises aren't really "executed". They can be constructed, or waited on to fulfill, or waited on to reject. If you have a Promise, you already have some ongoing code that will (probably) eventually result in a fulfillment value being assigned to the Promise.
Hanging Promises are syntactically permitted - values that resolve to Promises but which aren't interacted with elsewhere. (Which makes sense - every .then
or .catch
produces a new Promise. If every Promise had to be used by something else, you'd end up with an infinite regress.)
Doing
(async () => {
foo = await someOtherAsyncFunc();
console.log(foo);
})();
is essentially syntax sugar for
someOtherAsyncFunc()
.then((foo) => {
console.log(foo);
});
There's no need to tack anything else onto the end of either of those. (though it's recommended to add a .catch
to a dangling Promise so unhandled rejections don't occur)
ASYNC / AWAIT SyntaxError: await is only valid in async functions and the top level bodies of modules
A Promise
return something in the future so you need a way to wait for it.
// const CSVtoJSON = require("json2csv")
// const FileSystem = require("fs")
let temp = undefined;
async function test()
{
const data = await CSVtoJSON().fromFile('./input.csv')
// do your stuff with data
temp = data;
console.log(temp) // at this point you have something in.
return true
}
test();
console.log(temp) // at this point nothing was there.
How can I do top level await
You can do this to export a function dependent on a promise :
let getTokens = async () => {
var token = null;
var user_id = null;
if (typeof window !== 'undefined') { token = (await getItem('secure_token')); }
if (typeof window !== 'undefined') { user_id = (await getItem('user_id')); }
return {token, user_id };
}
export const API_ROUTE = getTokens().then((data)=> { return axios.create({
baseURL: API_URL,
withCredentials: true,
crossdomain: true,
headers: {
"Key": data.token,
"Id": data.user_id
}
}); });
You can use this promise to access the axios instance in your module.
how to assign a top-level const from a promise when top-level await is not available
You need to define you var with "let" instead of const, then assign the value in the "then".
let value;
f(x).then(val => value = val)
Maybe you have a better way to do this, but with the contexte we have, I would do it this way.
What will be the use of async keyword after the ES2022 release?
Top-level await does not replace async functions. You will still (and forever) be forbidden to do
const fn = () => {
await callApi();
};
or anything more complicated than that, because await can only used in an async function - and permitting await
on the top level doesn't change that.
Functions are the building blocks of modular programming. Top-level await is only there to make the syntax a bit easier - it doesn't fundamentally change the language or make functions obsolete.
Related Topics
Why Was the Arguments.Callee.Caller Property Deprecated in JavaScript
How to Find Events Bound on an Element with Jquery
How to Get the Size of a JavaScript Object
Convert String with Commas to Array
What Is the Meaning of the "At" (@) Prefix on Npm Packages
Detecting Arrow Key Presses in JavaScript
How to Upload a File with the Js Fetch API
Accessing Jpeg Exif Rotation Data in JavaScript on the Client Side
Jquery.Parsejson Throws "Invalid JSON" Error Due to Escaped Single Quote in JSON
Http Headers in Websockets Client API
How to Debug JavaScript/Jquery Event Bindings with Firebug or Similar Tools
How to Parse JSON Using Node.Js
Prevent Double Submission of Forms in Jquery
Jquery - Setting the Selected Value of a Select Control via Its Text Description
In JavaScript, Why Is "0" Equal to False, But When Tested by 'If' It Is Not False by Itself