Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are await and async valid variable names?

I was experimenting with how / is interpreted when around different keywords and operators, and found that the following syntax is perfectly legal:

// awaiting something that isn't a Promise is fine, it's just strange to do:
const foo = await /barbaz/
myFn()

Error:

Uncaught ReferenceError: await is not defined

It looks like it tries to parse the await as a variable name..? I was expecting

await is only valid in async function

or maybe something like

Unexpected token await

To my horror, you can even assign things to it:

const await = 'Wait, this actually works?';
console.log(await);

Shouldn't something so obviously wrong cause a syntax error, as it does with let, finally, break, etc? Why is this allowed, and what the heck is going on in the first snippet?

like image 986
CertainPerformance Avatar asked May 01 '19 09:05

CertainPerformance


People also ask

What are async and await and why it is used?

The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains. Async functions may also be defined as expressions.

Why we use async and await instead of promises?

Async/Await is used to work with promises in asynchronous functions. It is basically syntactic sugar for promises. It is just a wrapper to restyle code and make promises easier to read and use. It makes asynchronous code look more like synchronous/procedural code, which is easier to understand.

What is the difference between async and await?

The async keyword is used to define an asynchronous function, which returns a AsyncFunction object. The await keyword is used to pause async function execution until a Promise is fulfilled, that is resolved or rejected, and to resume execution of the async function after fulfillment.

Can you await a variable?

The await keyword is used to get a value from a function where you would normally use . then() . Instead of calling . then() after the asynchronous function, you would simply assign a variable to the result using await .


1 Answers

Reserved keywords cannot be used as identifiers (variable names). Unlike most other special Javascript words (like those listed in the question, let, finally, ...), await is not a reserved keyword, so using it as a variable name does not throw a SyntaxError. Why wasn't it made into a reserved keyword when the new syntax came out?

Backwards compatibility

Back in 2011, when ES5 was still a relatively new thing, code that used await (and async) as variable names was perfectly valid, so you may have seen something like this on a couple sites:

function timeout(ms) {
  var await = $.Deferred();
  setTimeout(await.resolve, ms);
  return await.promise();
};

The choice of that variable name may seem odd, but there was nothing wrong with it. await and async have never been reserved keywords - if the writers of the ES2017 specification made await into a reserved keyword, and browsers implemented that change, people visiting those older sites on newer browsers would not be able to use those sites; they would likely be broken.

So perhaps if they were made into reserved keywords, a few sites which chose a peculiar variable name wouldn't work properly - why should the existence of those sites permanently affect the future evolution of ECMAscript and result in confusing code like in the question?

Because browsers will refuse to implement a feature which breaks existing sites. If a user finds that a site does not work on one browser, but works on another, that will incentivize them to switch browsers - the maker of the first browser would not want that, because that would mean less market share for them, even if it's a feature which makes the language more consistent and understandable. In addition, the editors of the specification do not want to add something that will never be implemented (or will only be implemented sporadically), because then the specification would lose some of its status as a standard - contrary to its main goal.

You could see these interactions in action with Array.prototype.flatten and Array.prototype.contains - when browsers started shipping them, it was found that they broke a few existing sites due to name conflicts, so the browsers backed out of the implementation, and the specification had to be tweaked (the methods were renamed to .flat and .includes).


There actually is a situation in which await cannot be used as an identifier, which is inside of ES6 modules:

<script type="module">
  const await = 'Does it work?';
</script>

This is because while ES6 (ES2015) modules were being figured out, async/await was already on the horizon (initial commit for the async/await proposal can be seen at the beginning of 2014), so while designing modules, await could be made a reserved keyword in preparation for the future, without breaking any existing sites.


With regards to the first snippet in the question:

const foo = await /barbaz/
myFn()

This is syntactically valid because await is a valid variable name outside of async functions, and the interpreter thinks you're trying to divide, rather than use a regular expression:

const foo = await / barbaz / myFn()

Not relying on Automatic Semicolon Insertion would have identified the problem earlier, because the last / could not have been interpreted as division:

const foo = await /barbaz/;
myFn();

This exact somewhat-ambiguous situation was actually specifically brought up in a TC39 meeting on async/await:

YK: What are you worried about?

WH: Ambiguities on code sequences that start with await/ and then get interpreted in diverging ways (due to the await-as-identifier vs await-as-operator distinction that flips the / between division and starting a regexp) by cover grammars vs. real grammars. It's a potential bug farm.

like image 126
CertainPerformance Avatar answered Oct 19 '22 11:10

CertainPerformance