Using await outside of an async function - javascript

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?

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

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.

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.

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.

Related

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?

I have a short Node.js script where I require another package and call an async function from it and subsequently want to print the return value. If I simply await the return value from the top level, then I'll get an error, saying that I can only use await inside an async function itself. So apparently the way to go is like this:
async function main() {
foo = await someOtherAsyncFunc();
console.log(foo);
}
main()
Or:
(async function() {
foo = await someOtherAsyncFunc();
console.log(foo);
})();
Or:
(async () => {
foo = await someOtherAsyncFunc();
console.log(foo);
})();
(Credit to VLAZ in chat https://chat.stackoverflow.com/transcript/message/54186176#54186176)
This works - but I want to understand the reasons behind it a little bit more: I'm used to not being able to directly use await from the top level. However, I'm also used to having to call some special library function to actually "venture" into async from the top level. In Python, see asyncio.run for example. What's the point of requiring await to be inside an async function - if I can then call just any async function from the top level? Why then isn't await available at top level, too?
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)

Do promises resolved during module init run before scripts (with top level await?)

When using webpack (v5 via webpacker v6) and exporting an interface (e.g. via globals) to other scripts on a webpage do promises resolved during module initialization necessarily execute before the scripts on the page access them? Is there a way to ensure they do? Would top level await work?
Specifically, imagine I have a module that's exported to the global Library FOO that loos like the following
---In module getting webpacked---
class Foo { ... }
const done = (async function() {
Foo.bar = 10 - (await prom)/(await other_prom)
})()
resolve(5); other_resolve(80); //resolve for prom/other_prom
await done //if we have top level await
export {Foo}
--- In (unprocessed) script tag later ---
new FOO.Foo(20); //This shouldn't execute until AFTER the async function modifies Foo
I'd like new FOO.Foo(20) to be guaranteed not to executed until after the async function up above completes executing. Is there a way to be guaranteed this won't be executed before the initialization is done other than wrapping all page javascript in an async function and awaiting all the initializing promises? At that point I might as well just give up on the niceties of async and write callback functions.
exporting an interface via globals to other scripts on a webpage
No, don't do that. It's neither a good practice to use globals, nor will top-level-await make anything wait. And non-module scripts can't use top-level-await anyway.
Instead, if you do use modules like
export class Foo { ... }
Foo.bar = 10 - (await Promise.resolve(5)/(await Promise.resolve(80));
import * as FOO from 'the-first-module';
new FOO.Foo(20);
then yes, it will work as expected and not run new Foo before the imported script has finished its asynchronous evaluation.

top-level-await error when using NextJS API

I'm building an api with NextJS and MongoDB. I do a basic setup on the top of the API file:
const { db } = await connectToDatabase();
const scheduled = db.collection('scheduled');
and I continue the code with my handler function:
export default async function handler(req, res) {
otherFunctionCalls()
...
}
const otherFunctionCalls = async () => {
...
}
I know await will work only within an async function, but I would like to use the scheduled constant in other functions what the handler calls, that's why I need to call it on the top.
If I put the constant to every single function, then it's code duplication.
What's the best practice to access to scheduled constant? Should I add the otherFunctionCalls declaration into the handler function?
The complete error what I got:
Module parse failed: The top-level-await experiment is not enabled (set experiments.topLevelAwait: true to enabled it)
File was processed with these loaders:
* ./node_modules/next/dist/build/babel/loader/index.js
You may need an additional loader to handle the result of these loaders.
Error: The top-level-await experiment is not enabled (set experiments.topLevelAwait: true to enabled it)
In the comments you've said you want to find another solution rather than enabling the top-level await experiment in the tool you're using.
To do that, you'll have to adjust the module code to handle the fact that you don't have the scheduled collection yet, you just have a promise of it. If the only exported function is handler and all of the other functions are going to be called from handler, they it makes sense to handle that (no pun!) in handler, along these lines:
// A promise for the `scheduled` collection
const pScheduled = connectToDatabase().then(db => db.collection("scheduled"));
export default async function handler(req, res) {
const scheduled = await pScheduled;
await otherFunctionCalls(scheduled);
// ...
}
const otherFunctionCalls = async (scheduled) => {
// ...use `scheduled` here...
};
There are lots of ways you might tweak that, but fundamentally you'll want to get the promise (just once is fine) and await it to get its fulfillment value anywhere you need its fulfillment value. (For the avoidance of double: await doesn't re-run anything; if the promise is already fulfilled, it just gives you back the fulfillment value the promise already has.)

JS basic design when I have naturaly sync code but I need to call a few async functions

Could you please help me to understand javascirpt async hell?
I think I am missing something important ☹ The thing is that js examples and most of the answers on the internet are related to just one part of code – a small snippet. But applications are much more complicated.
I am not going write it directly in JS since I am more interested of the design and how to write it PROPERLY.
Imagine these functions in my application:
InsertTestData();
SelectDataFromDB_1(‘USERS’);
SelectDataFromDB_2(‘USER_CARS’,’USERS’);
FillCollections(‘USER’,’USER_CARS’);
DoTheWork();
DeleteData();
I did not provide any description for the functions but I think it is obvious based on names. They need to go in THIS SPECIFIC ORDER. Imagine that I need to run a select into the db to get USERS and then I need run a select to get USER_CARS for these USERS. So it must be really in this order (consider the same for other functions). The thing is that need to call 6 times Node/Mysql which is async but I need results in specific order. So how can I PROPERLY make that happen?
This could work:
/* not valid code I want to present the idea and keep it short */
InsertTestData(
Mysql.query(select, data, function(err,success)
{
SelectDataFromDB_1(‘USERS’); -- in that asyn function I will call the next procedure
}
));
SelectDataFromDB_1 (
Mysql.query(select, data, function(err,success)
{
SelectDataFromDB_2(‘USERS’); -- in that asyn function I will call the next procedure
}
));
SelectDataFromDB_2 (
Mysql.query(select, data, function(err,success)
{
FillCollections (‘USERS’); -- in that asyn function I will call the next procedure
}
));
etc..
I can “easily” chain it but this looks as a mess. I mean really mess.
I can use some scheduler/timmers to schedule and testing if the previous procedure is done)
Both of them are mess.
So, what is the proper way to do this;
Thank you,
AZOR
If you're using a recent version of Node, you can use ES2017's async/await syntax to have synchronous-looking code that's really asynchronous.
First, you need a version of Mysql.query that returns a promise, which is easily done with util.promisify:
const util = require('util');
// ...
const query = util.promisify(Mysql.query);
Then, wrap your code in an async function and use await:
(async () => {
try {
await InsertTestData();
await SelectDataFromDB_1(‘USERS’);
await SelectDataFromDB_2(‘USER_CARS’,’USERS’);
await FillCollections(‘USER’,’USER_CARS’);
await DoTheWork();
await DeleteData();
} catch (e) {
// Handle the fact an error occurred...
}
})();
...where your functions are async functions, e.g.:
async InsertTestData() {
await query("INSERT INTO ...");
}
Note the try/catch in the async wrapper, it's essential to handle errors, because otherwise if an error occurs you'll get an unhandled rejection notice (and future versions of Node may well terminate the process). (Why "unhandled rejections"? Because async functions are syntactic sugar for promises; an async function returns a promise.) You can either do that with the try/catch shown, or alternate by using .catch on the result of calling it:
(async () => {
await InsertTestData();
// ...
})().catch(e => {
// Handle the fact an error occurred
});

Is it possible to use await without async in Js

Await is a amazing feature in es7.
However,everytime I use await I found that I have to define a async function and call this function.
Such as
async function asy(){
const [resCityGuess,resCityHot,resCityAll]=await Promise.all([
this.http.get('api/v1/cities?type=guess'),
this.http.get('api/v1/cities?type=hot'),
this.http.get('api/v1/cities?type=group')
])
this.cityGuessName=resCityGuess.data.name;
this.cityGuessId=resCityGuess.data.id;
this.cityHot=resCityHot.data;
this.cityAll=resCityAll.data;
}
asy.apply(this);
What I want is use await without async function such as
// the async function definition is deleted
const [resCityGuess,resCityHot,resCityAll]=await Promise.all([
this.http.get('api/v1/cities?type=guess'),
this.http.get('api/v1/cities?type=hot'),
this.http.get('api/v1/cities?type=group')
])
this.cityGuessName=resCityGuess.data.name;
this.cityGuessId=resCityGuess.data.id;
this.cityHot=resCityHot.data;
this.cityAll=resCityAll.data;
// without call fn
I think define the function fn and call this fn is repeated sometimes so I want to know is it possible to optimize the situation?
Can I use await without async?
Thank you so much!
No. The await operator only makes sense in an async function.
edit — to elaborate: the whole async and await deal can be thought of as being like a LISP macro. What that syntax does is inform the language interpretation system of what's going on, so that it can in effect synthesize a transformation of the surrounding code into a Promise-based sequence of callback requests.
Thus using the syntax is an implicit short-cut to coding up the explicit Promise stuff, with calls to .then() etc. The runtime has to know that a function is async because then it knows that await expressions inside the function need to be transformed to return Promises via a generator mechanism. And, for overlapping reasons, the async decoration on the function declaration tells the language that this is really a function that returns a Promise and that it needs to deal with that.
So, it's complicated. The process of improving and extending JavaScript has to account for the fact that there's an unimaginably massive amount of JavaScript code out in the world, and so in almost all cases no new feature can cause a page untouched since 2002 to fail.
edit — Now, here in 2021, there are rules for how an await call works in the outer level of a module. It's not quite the same as how it works in an async function situation, but it's similar.
Solution
Not exactly without async but take a look, this may illuminate.
You can effectively create an arrow function (that is intentionally anonymous).
Arrow functions are an ECMAScript2015 syntax for writing anonymous functions. They have a lot of features that separate them from the original function keyword in JavaScript, but in many cases they can be a drop-in replacement for anonymous functions.
Add the async keyword, surround the function with parentheses and add another parentheses set to run the function.
Inside the function, use the await keyword.
Like this:
(async () => {
console.log("Message in 5s");
await new Promise((resolve) => setTimeout(() => resolve(), 5000));
console.log("If you like it, show it");
})();
It's proposed to ECMAScript.
Chrome/Chromium (and anything with an up-to-date V8-based JS engine) has a working implementation that appears to be compliant with the specification.
The proposal itself is at stage 3.
More info:
https://github.com/tc39/proposal-top-level-await
https://v8.dev/features/top-level-await
Top-level await (await without async) is not yet a JavaScript feature.
However, as of version 3.8, it can be used in Typescript.
In order to use it, the following configuration is required (in tsconfig.json):
"module": "esnext" // or "system"
"target": "es2017" // or higher
More information:
https://typescript.tv/new-features/top-level-await-in-typescript-3-8/

Categories

Resources