Why await is only allowed in an async function?

Hi.
I just started to learn about Promise in Javascript.
I completely understand what an async function is but why using the await keyword outside them is not allowed? It makes complete sense to use it.

In fact, I am coming from the C++ world which you can do pretty much everything you like. You can create and destroy threads manually without all these limitations in Javascript.

I will be glad if someone can help me understand “Why using the await keyword is not allowed in a non-async function?”

Sorry for poor English.

It is allowed since node v14.8

1 Like

So do you have any idea why it was not allowed in previous versions?

the probably underwhelming answer is that the language wasn’t build like that

2 Likes

But there should always be a reason.
At least I don’t like making random decisions when building a language.

you would need to ask to the creators, I wouldn’t know where to search for such an information

1 Like

Yes, you are right. and as @jenovs mentioned, this feature is added by default.
And that is what creators have decided to do.

More on that:

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

Thanks to every one.

At least I don’t like making random decisions when building a language.

Well, perhaps you have more experience building languages than I do, but it seems to me that first of all, this isn’t random, it’s at best arbitrary. And in building a language, you’re going to have to make A LOT of arbitrary decisions. I think it would be impossible to build a language that would work in every way that anyone could want with any possible syntax. Decisions have to be made.

1 Like

Right, but that’s a different language. It would be like saying that we should be allowed to stop using pronouns in English because many romance languages let you do that. But they are different languages with different rules. JavaScript is JavaScript. It’s not C++, it’s not C, it’s not Java. It’s JavaScript and needs to be learned and understood as such. Now, because all those languages are C based, they will have some overlap, but it can’t be assumed.

Actually, I never made a language. I was looking at “making a language” just like how I look at other software projects which I’ve done a few of them.

But for me, it makes no sense to “only allow await in async functions”.
Because I think it is completely ok to have:

function this_returns_promise(){
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("done!");
            resolve();
        }, 2000);
    });
}

await this_returns_promise();

Just wait until that promise is done.
To me, it looks like the thread.join() method in a lot of languages and libraries.
This method basically waits until the other thread is done and there is no limitation like “you can only call this method from async functions”.

For what it’s worth, “C does it” or “C++ does it” is a terrible reason to do anything. I love working in C, but C/C++ lets you do some massively stupid things.

I think you are missing the idea that JS is single threaded. This code runs on a single thread, so there is no ‘waiting until the other thread is done’ in this snippit. Your thread based mental model doesn’t quite work for JS.

I look at this with two assumptions:

  1. This language was created by smart people. Most of them undoubtedly have a background in those other languages.
  2. People don’t do extra work if they don’t have to.

From this, I infer that the initially decision to not allow await outside of an async function was a purposeful one. It is because when they decided to add async/await, it was deemed that it would take either too much work to allow it (at that point) or it might have some side-effect. I don’t think they sat around and said, “Yeah, it should work fine anywhere, but we’re going to do the extra work to restrict it just for fun.” I think that that is a logical inference.

2 Likes

But I think we can think of them as “virtual threads”.
For example, some computers only have 2 physical cores. But it can run hundreds of processes at the same time. Because the operating system will automatically distribute instructions from different processes to different cores (threads).

Isn’t a promise in javascript handled just like a process that is handled by the operating system?

For example, what side effect?

I don’t know, I wasn’t in the room. Perhaps someone has written a book, The Logic Behind Every Decision Made While Building JavaScript. Or maybe you can. I don’t really care that much.

My point was that there was probably some reason to disallow it (again, assuming people don’t add work for no reason). A side-effect (like breaking something else) could be a possible reason. I don’t know.

But again, people don’t usually add work if they don’t have to, so there was probably some reason. It really doesn’t interest me enough to dig in and find out why. Even if there was no good reason, who cares? It’s a moot point now.

1 Like

Sure, but a single JS instance has a single main event loop and can only do one thing at a time.

Promises were designed for separate pieces of hardware interacting. You can get separate JS instances interacting on one piece of hardware, but this is different than a single instance of a C or C++ code managing multiple threads.

That’s excellent point - even if JS looks a lot like C++, there are some big differences in how it works.

Because to implement it at a higher level means changing how the environment the code executes in works, which has to wait for browser vendors to rebuild how parts of their JS runtime works. It can be done naïvely quite easily – just execute all code in the program inside an async closure which could be added by something like Babel – but this is not really a great solution.

Async await on its own is just sugar for promises, and also can be implemented entirely using generators, and generators can be implemented using [lots of] basic JS, so is completely backwards compatible once there is a syntax transform for the keywords using something like Babel. It doesn’t need the runtime rebuilt.

And async await came first: you have the await keyword which can be used in a scope where the async keyword is applied. That’s how it works. So if you then realise, actually, it would be useful to have top-level await, need to re-engineer how the whole thing works in certain situations.

Nope, JS is single threaded, it puts it into a queue, and the program continues, and when the promise is resolved the JS engine will attempt to pop it off the queue when there is a free tick.

JS can have things on different system threads by using workers, but that is a completely different thing, fairly unwieldy and they have to send everything they do back as a message to the main thread.

Node had a few threading libraries when it first appeared: it’s a C++ codebase so people just made extensions. Mostly been nixed as they break the model of JS.

WASM will allow threads at some point, but WASM code, although it’s C-level basically, is heavily sandboxed with only specific allowed interfaces between it and “normal” JS code, it’s not general purpose.

They aren’t making random decisions: the overriding principle is that everything that already exists in the language has to work the way it did before, as much as that is possible. This means that, once async await was ratified and part of the language, the way it actually works can’t be drastically adjusted. So if the behaviour of async await is changed (which it has been to an extent, although in some respects top level await is not exactly the same thing as await in async await), it cannot a. break the existing behaviour at all and b. cannot introduce performance penalties in the existing behaviour

Edit: as examples of last point, you cannot have a new version of JS in the same way as a new version of, say, Python. You can only add to what already exists. So any error in design can’t be directly rectified. var has numerous problems, but the behaviour of var cannot be altered, so they added two new keywords (const and let). The standard Date object is broken in numerous ways, and so an entirely new standard object (Temporal) is currently proposed for addition. The array method flat couldn’t be called the more sensible flatten because that name was taken by a (jQuery competitor) library called Prototype years ago, and that library’s method has very slightly different semantics to the new standard method. And so on.

1 Like

Array#flat was added in ES2019, so I’m pretty sure Prototype got zero consideration there.