Why is `async` not a a reserved word? - javascript

As far as I can tell, both the spec and the documentation have await as the only reserved keyword out of the async/await feature.
This is further demonstrated by the fact that we can name a variable async:
For example:
var async = 5;
console.log(async) // this is fine
Node (6.10) (also on Repl.it)
Chrome (59)
Firefox (54)
Is it because of backwards compatibility? I'd guess many codebases would use the name async for certain features.
This allows for some strange looking code examples:
async function async() {
var async = 5;
await async;
return async;
}
async().then(console.log)
Infinite recursive promise chain? (Not really important since any function name would allow this, however this code looks additionally confusing)
async function async() {
await async();
}
// stackoverflow (might need to open your console to see the output)

async does not need to be a reserved word, because it can be uniquely identified. The contexts in which it can occur are those such as
async function() { }
async () => { }
obj = { async foo() { } };
All of these could not be parsed in any way other than seeing async as indicating an async function.
On the other hand, await can in theory be used in a statement such as
async function foo() {
await(1);
}
which is ambiguous; is await awaiting the value 1, or is it a function being called with the parameter 1? Therefore, await needs to be a reserved word (inside async functions; outside, feel free to use it as a variable).
Remember that JavaScript has evolved greatly since its origin. Many words were designated as reserved, and then never used, or designated as reserved when technically they might not need to have been. The designation of await as a reserved word (within modules), and the non-designation of async as a reserved word, are the product of a more mature understanding of the language on the part of its designers.

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)

Use 'await' in parameter default in an async function

I want to use an async function as a default parameter but I get an error: 'await' is not a valid idientifier name in an async function. Is this a language limitation or have I missed something?
Background
There is support for using functions to default parameters in a pretty neat way:
> a = ({ no }, b = (function(c) { return c -1; })(no)) => console.log(no,b);
[Function: a]
> a({ no: 2 })
2 1
This could allow for passing results from expensive database calls while retaining the option of getting the parameters if null. This requires though that we can do an await inside the parameter call:
> a = async ({ no }, b = await (async function(c) { return c -1; })(no)) => console.log(no,b);
a = async ({ no }, b = await (async function(c) { return c -1; })(no)) => console.log(no,b);
^^^^^
SyntaxError: 'await' is not a valid identifier name in an async function
I suppose that this is a feature from async/await being syntactic sugar for Promises and rewriting the above as a Promise would be rather challenging. Still, the pattern would be really neat and If I've missed something, please enlighten me.
Thanks!
It's a limitation, specifically in this case AwaitExpression is not allowed in CoverCallExpressionAndAsyncArrowHead (which includes the formal parameters). The second bullet point is:
It is a Syntax Error if CoverCallExpressionAndAsyncArrowHead Contains AwaitExpression is true.
...but the same language is present for the various other function definitions.
I probably don't have to tell you that you can work around this by just not awaiting the async function until within the body:
const a = async ({ no }, b = (async function(c) { return c -1; })(no)) => {
// Removed await ------------^
b = await b; // <== Then added it back within the function body
console.log(no,b);
};
a({no: 3});
Using an await expression in a default parameter is a fascinating thought. I suspect the reason it's not (yet?) allowed is primarily complexity management. (This is speculation on my part, take it with a grain of salt.) The beginning of an async function is synchronous (that's how you start your asynchronous operation, after all), it only becomes async as of the first await or return (or implicit return at the point of falling off the end of the function). Having it become async prior to the beginning of the function body adds a fair bit of complexity. Not that it couldn't be done. :-)
However, looking through the closed issues on the proposal, an issue was raised asking what happens when a default parameter initialization throws an exception, should it throw or return a rejected promise? Which led to a TC39 discussion in which it was determined that it should return a rejected promise, thus this issue, the resolution of which moved evaluation of the parameter initialization into the function body (so exceptions could be part of the promise). Which would seem, to my mind, to open up the possibility of allowing await in the formal parameters of an async function...

JS async/await - why does await need async?

Why does using await need its outer function to be declared async?
For example, why does this mongoose statement need the function it's in to return a promise?
async function middleware(hostname, done) {
try {
let team = await Teams.findOne({ hostnames: hostname.toLowerCase() }).exec();
done(null, team);
} catch (err) { done(err); }
}
I see the runtime/transpiler resolving the Teams promise to it's value and async signaling it "throws" rejected promises.
But try/catch "catches" those rejected promises, so why are async and await so tightly coupled?
I'm not privy to the JavaScript language design discussions, but I assume it's for the same reasons that the C# language requires async (also see my blog).
Namely:
Backwards compatibility. If await was suddenly a new keyword everywhere, then any existing code using await as a variable name would break. Since await is a contextual keyword (activated by async), only code that intends to use await as a keyword will have await be a keyword.
Easier to parse. async makes asynchronous code easier to parse for transpilers, browsers, tools, and humans.
Copied from https://stackoverflow.com/a/41744179/1483977 by #phaux:
These answers all give valid arguments for why the async keyword is a
good thing, but none of them actually mentions the real reason why it
had to be added to the spec.
The reason is that this was a valid JS pre-ES7
function await(x) {
return 'awaiting ' + x
}
function foo() {
return(await(42))
}
According to your logic, would foo() return Promise{42} or
"awaiting 42"? (returning a Promise would break backward
compatibility)
So the answer is: await is a regular identifier and it's only
treated as a keyword inside async functions, so they have to be marked
in some way.
Fun fact: the original spec proposed more lightweight function^ foo()
{} for async syntax.
Because using await inside middleware function means the middleware function can't return a result immediately (it must wait until await is settled) and middleware function callers must wait until the promise (returned from middleware function) is settled.

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/

ES2017 - Async vs. Yield

I am confused about the current discussion of adding async functions and the keyword await to the next EcmaScript.
I do not understand why it is necessary to have the async keyword before the function keyword.
From my point of view the await keyword to wait for a result of a generator or promise done, a function's return should be enough.
await should simple be usable within normal functions and generator functions with no additional async marker.
And if I need to create a function what should be usable as an result for an await, I simply use a promise.
My reason for asking is this good explanation, where the following example comes from:
async function setupNewUser(name) {
var invitations,
newUser = await createUser(name),
friends = await getFacebookFriends(name);
if (friends) {
invitations = await inviteFacebookFriends(friends);
}
// some more logic
}
It also could be done as normal function, if the execution of a function will wait for finishing the hole function until all awaits are fulfilled.
function setupNewUser(name) {
var invitations,
newUser = await createUser(name),
friends = await getFacebookFriends(name);
if (friends) {
invitations = await inviteFacebookFriends(friends);
}
// return because createUser() and getFacebookFriends() and maybe inviteFacebookFriends() finished their awaited result.
}
In my opinion the whole function execution is holding until the next tick (await fulfillment) is done. The difference to Generator-Function is that the next() is triggering and changing the object's value and done field. A function instead will simple give back the result when it is done and the trigger is a function internal trigger like a while-loop.
I do not understand why it is necessary to have the async keyword before the function keyword.
For the same reason that we have the * symbol before generator functions: They mark the function as extraordinary. They are quite similar in that regard - they add a visual marker that the body of this function does not run to completion by itself, but can be interleaved arbitrarily with other code.
The * denotes a generator function, which will always return a generator that can be advanced (and stopped) from outside by consuming it similar to an iterator.
The async denotes an asynchronous function, which will always return a promise that depends on other promises and whose execution is concurrent to other asynchronous operations (and might be cancelled from outside).
It's true that the keyword is not strictly necessary and the kind of the function could be determined by whether the respective keywords (yield(*)/await) appear in its body, but that would lead to less maintainable code:
less comprehensible, because you need to scan the whole body to determine the kind
more errorprone, because it's easy to break a function by adding/removing those keywords without getting a syntax error
a normal function, whose execution will wait for finishing the hole body until all awaits are fulfilled
That sounds like you want a blocking function, which is a very bad idea in a concurrent setting.
By marking a function as async, you're telling JS to always return a Promise.
Because it will always return a Promise, it can also await on promises inside of its own block. Imagine it like one giant Promise chain - what happens internally to the function gets effectively gets bolted on to its internal .then() block, and what's returned is the final .then() in the chain.
For example, this function...
async function test() {
return 'hello world';
}
... returns a Promise. So you can execute it like one, .then() and all.
test().then(message => {
// message = 'hello world'
});
So...
async function test() {
const user = await getUser();
const report = await user.getReport();
report.read = true
return report;
}
Is roughly analogous to...
function test() {
return getUser().then(function (user) {
return user.getReport().then(function (report) {
report.read = true;
return report;
});
});
}
In both cases, the callback passed to test().then() will receive report as its first parameter.
Generators (i.e. marking a function * and using the yield keyword) are a different concept altogether. They don't use Promises. They effectively allow you to 'jump' between different portions of your code, yielding a result from inside of a function and then jumping back to that point and resuming for the next yield block.
Although they feel somewhat similar (i.e. 'halting' execution until something happens somewhere else), async/await only gives you that illusion because it messes with the internal ordering of Promise execution. It's not actually waiting - it's just shuffling when the callbacks happen.
Generators, by contrast, are implemented differently so that the generator can maintain state and be iterated over. Again, nothing to do with Promises.
The line is further blurred because at the current time of writing, support for async/await is scare; Chakracore supports it natively, and V8 has it coming soon. In the meantime, transpilers like Babel allow you to write async/await and convert the code to generators. It's a mistake to conclude that generators and async/await are therefore the same; they're not... it just so happens that you can bastardize how yield works alongside Promises to get a similar result.
Update: November 2017
Node LTS now has native async/await support, so you ought never to need to use generators to simulate Promises.
These answers all give valid arguments for why the async keyword is a good thing, but none of them actually mentions the real reason why it had to be added to the spec.
The reason is that this was a valid JS pre-ES7
function await(x) {
return 'awaiting ' + x
}
function foo() {
return(await(42))
}
According to your logic, would foo() return Promise{42} or "awaiting 42"? (returning a Promise would break backward compatibility)
So the answer is: await is a regular identifier and it's only treated as a keyword inside async functions, so they have to be marked in some way.
The reason for the async keyword in front is simple so you know that return value will be transformed into a promise. If no keyword how would the Interpreter know to do this.
I think this was first introduce in C# and EcmaScript is taking a loot of stuff from TypeScript. TypeScript and C# are conceived by Anders Hejlsberg and are similar.
lets say you have a function (this one is just to have some asynchronous work)
function timeoutPromise() {
return (new Promise(function(resolve, reject) {
var random = Math.random()*1000;
setTimeout(
function() {
resolve(random);
}, random);
}));
}
this function will make us wait for a random time and return a Promise (if you use jQuery Promise is similar to Deferred) object. To use this function today you would write something like this
function test(){
timeoutPromise().then(function(waited){
console.log('I waited' + waited);
});
}
And this is fine. Now lets try to return the log message
function test(){
return timeoutPromise().then(function(waited){
var message = 'I waited' + waited;
console.log(message);
return message; //this is where jQuery Deferred is different then a Promise and better in my opinion
});
}
Ok this is not bad but there are two return statements and a function in the code.
Now with async this will look like this
async function test(){
var message = 'I waited' + (await timeoutPromise());
console.log(message);
return message;
}
The code is short and inline. If you written a lot of .then() or . done() you know how unreadable the code can get.
Now why the async keyword in front of function. Well this is to indicate that your return value is not what is returned. In theory you could write this(This can be done in c# I don't know if js will allow since it's not done).
async function test(wait){
if(wait == true){
return await timeoutPromise();
}
return 5;
}
you see, you return a number but the actual return will be a Promise you don't have to use
return new Promise(function(resolve, reject) { resolve(5);};
Since you can't await a number only a Promise await test(false) will throw an exception and await test(true) will not if you don't indicate async in front.

Categories

Resources