Using await outside of an async function
Asked Answered
F

4

131

I was attempting to chain two async functions together, because the first had a conditional return parameter that caused the second to either run, or exit the module. However, I've found odd behavior I can't find in the specs.

async function isInLobby() {
    //promise.all([chained methods here])
    let exit = false;
    if (someCondition) exit = true;
}

This is a bastardized snippet of my code (you can see the full scope here), that simply checks if a player if already in a lobby, but that's irrelevant.

Next we have this async function.

async function countPlayer() {
    const keyLength = await scardAsync(game);
    return keyLength;
}

This function doesn't need to run if exit === true.

I tried to do

const inLobby = await isInLobby();

This I hoped would await to results, so I can use inLobby to conditionally run countPlayer, however I received a typeerror with no specific details.

Why can't you await an async function outside of the scope of the function? I know it's a sugar promise, so it must be chained to then but why is it that in countPlayer I can await another promise, but outside, I can't await isInLobby?

Fjeld answered 24/9, 2016 at 18:12 Comment(6)
Can you show us where you did await isInLobby(), and how inLobby is used? Also, where/how is countPlayer called?Jason
@Jason I linked my repo for the actual context. Too much code to put into the questionFjeld
I don't see where the problem is with that (maybe you already updated the repo)? If you refer to the isInLobby().then( … countPlayer().then … part, the solution is trivial: just make the function in which those calls are contained (the (req, res) => one) async.Jason
@Jason the issue is not that it was broken, it works as is. I just didn't understand why top-level awaiting wasn't a thing. Turns out it's just not existent yet without scoping your entire module as an async functionFjeld
But you don't even need top-level await for your code at all? That's why I wondered that you accepted the answer that doesn't really relate to the problem in the question.Jason
Well, the question was "Why can't you await an async function outside of the scope of the function?" so I believed it to be relevant, I think.Fjeld
S
93

Top level await is not supported. There are a few discussions by the standards committee on why this is, such as this Github issue.

There's also a thinkpiece on Github about why top level await is a bad idea. Specifically he suggests that if you have code like this:

// data.js
const data = await fetch( '/data.json' );
export default data;

Now any file that imports data.js won't execute until the fetch completes, so all of your module loading is now blocked. This makes it very difficult to reason about app module order, since we're used to top level Javascript executing synchronously and predictably. If this were allowed, knowing when a function gets defined becomes tricky.

My perspective is that it's bad practice for your module to have side effects simply by loading it. That means any consumer of your module will get side effects simply by requiring your module. This badly limits where your module can be used. A top level await probably means you're reading from some API or calling to some service at load time. Instead you should just export async functions that consumers can use at their own pace.

Sociability answered 24/9, 2016 at 18:16 Comment(7)
That's a good link, thank you. It's a shame top level support isn't there. I hope it is. Currently as it stands I have to nest my promises here and that is very bad practice and I don't like it. :( Thank you.Fjeld
@SterlingArcher alternatively, use an async IIFE: void async function() { const inLobby = await isInLobby() }()Fidelity
@Fidelity wouldn't that scope inLobby to the function making it non-accessible?Fjeld
@SterlingArcher yes it would, it would require you to move all your code there (basically making it the "top level"). It's just an alternative to using .then() (sorry, should have made that a bit more clear).Fidelity
dont agree, user of data.js is 'blocked' nevertheless while loading data.js itself and all of its dependencies, this notion of 'blocking' is not bad by itself. top level await could be considered as loading some dependency which one appearntly needs to have before usage is 'released'.Contango
This is no longer true. Top-level await is supported in Chrome and Firefox and Safari 15 and NodeJS.Eroto
In my case, I am using dynamic import which I think is a valid use case.Polyhydroxy
A
203

There is always this of course:

(async () => {
    await ...

    // all of the script.... 

})();
// nothing else

This makes a quick function with async where you can use await. It saves you the need to make an async function which is great! //credits Silve2611

Anzus answered 27/6, 2017 at 22:48 Comment(7)
Please elaborate further and explain why this works.Kaule
it is very stupid to Downvote this answer. It Makes a quick function with async where you can use await. It saves you the need to make an async function which is great!Cressi
Doesn't solve the problem since you still need to await this anonymous function, which again, doesn't work from outside of functions.Eucalyptus
so how would this handle an error condition? does it also land inside the await code?Piscina
Tks this is greate help, specially to callback promise once fetch login API, which is reponse back perfectly to get storage getItemFrieda
@Cressi I'm really late, but basically, it creates an anonymous async function and executes it. It pretty much defines a function and runs it, using arrow function syntaxGruff
This kind of function in Javascript/Node is called IIFE (Immediately Invoked Function Expression). Keep in mind that this functions will be serial only in its own body, nothing in the code will care about awaiting the end of execution of this piece of code. I only use this on the entrypoint of my applications.Marseillaise
S
93

Top level await is not supported. There are a few discussions by the standards committee on why this is, such as this Github issue.

There's also a thinkpiece on Github about why top level await is a bad idea. Specifically he suggests that if you have code like this:

// data.js
const data = await fetch( '/data.json' );
export default data;

Now any file that imports data.js won't execute until the fetch completes, so all of your module loading is now blocked. This makes it very difficult to reason about app module order, since we're used to top level Javascript executing synchronously and predictably. If this were allowed, knowing when a function gets defined becomes tricky.

My perspective is that it's bad practice for your module to have side effects simply by loading it. That means any consumer of your module will get side effects simply by requiring your module. This badly limits where your module can be used. A top level await probably means you're reading from some API or calling to some service at load time. Instead you should just export async functions that consumers can use at their own pace.

Sociability answered 24/9, 2016 at 18:16 Comment(7)
That's a good link, thank you. It's a shame top level support isn't there. I hope it is. Currently as it stands I have to nest my promises here and that is very bad practice and I don't like it. :( Thank you.Fjeld
@SterlingArcher alternatively, use an async IIFE: void async function() { const inLobby = await isInLobby() }()Fidelity
@Fidelity wouldn't that scope inLobby to the function making it non-accessible?Fjeld
@SterlingArcher yes it would, it would require you to move all your code there (basically making it the "top level"). It's just an alternative to using .then() (sorry, should have made that a bit more clear).Fidelity
dont agree, user of data.js is 'blocked' nevertheless while loading data.js itself and all of its dependencies, this notion of 'blocking' is not bad by itself. top level await could be considered as loading some dependency which one appearntly needs to have before usage is 'released'.Contango
This is no longer true. Top-level await is supported in Chrome and Firefox and Safari 15 and NodeJS.Eroto
In my case, I am using dynamic import which I think is a valid use case.Polyhydroxy
W
25

As of Node.js 14.3.0, the top-level await is supported with a flag:

--experimental-top-level-await

As of Node.js 16.12.0 / 17.0.0, no flag required.

Further details: https://v8.dev/features/top-level-await.

Wakayama answered 25/5, 2020 at 6:28 Comment(0)
P
6

you can do top level await since typescript 3.8
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#-top-level-await
From the post:
This is because previously in JavaScript (along with most other languages with a similar feature), await was only allowed within the body of an async function. However, with top-level await, we can use await at the top level of a module.

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

// Make sure we're a module
export {};

Note there’s a subtlety: top-level await only works at the top level of a module, and files are only considered modules when TypeScript finds an import or an export. In some basic cases, you might need to write out export {} as some boilerplate to make sure of this.

Top level await may not work in all environments where you might expect at this point. Currently, you can only use top level await when the target compiler option is es2017 or above, and module is esnext or system. Support within several environments and bundlers may be limited or may require enabling experimental support.

Prewar answered 25/2, 2020 at 19:34 Comment(2)
This is true of vanilla JavaScript as well. In node, you'll want to make sure you have "type": "module" at the top of your package.json, or use the .jsm file extension.Sandra
.jsm has never been a valid file extension. It's .mjs.Burst

© 2022 - 2024 — McMap. All rights reserved.