2 ms·
> I've also read that await() basically preempts the currently running function. How does this work? So async/await work by having the compiler do a mechanical
by jcranmer 5y ago
> I've also read that await() basically preempts the currently running function. How does this work?
So async/await work by having the compiler do a mechanical transformation of the function. If you have code that looks like this:
async function foo() {
let x = await bar();
await baz(x);
}
The actual code that gets generated is something like this (this is off the top of my head, forgive any transcription errors):
function foo() {
let x = undefined;
return Promise.resolve(null)
.then(() => bar())
.then(function (val) {
x = val;
return baz(x);
});
}
In other words, the async and await keywords work together to tell the compiler how to properly transform the function into the necessary async API: the async keyword says that the function needs this mangling, and the await keyword tells the compiler where the possible re-entry point is.
So, with that in mind, let's answer your main question:
> Why can't non-async functions use await()?
The shortest answer is that the semantics of doing the necessary transformation to support await in a function which doesn't opt into that transformation are hard to design and even harder to use.
Fundamentally, adding async to a function changes the API of the function [1]. In JS, it causes a function to return Promise<T> instead of T, and this API change can be done even without having any await keyword in the body of the function. Similar changes happen for other languages, just substitute Promise for whatever variant of will-resolve-in-future API is in that language.
In principle, it's possible to support such a scenario by making such an await just "run" the promise to completion. But here you run into issues with how the runtime handles asynchronous tasks. Is it possible to spin a nested event loop? Not necessarily, it depends on the runtime. If your synchronous call is already on a task (so there's basically an async function on the callstack), you would really want to basically inline all of the function calls so that the await causes the same transformation were it to be called directly in that async function. But such inlining is impossible (due to dynamic function calls).
To really delve deep into the details here requires a fuller understanding of how asynchronous runtimes work in general, and of the particulars of the various implementations. Async/await is a wonderful innovation in that it allows programmers to write asynchronous code state machines as if they were generally writing synchronous code. But it doesn't do anything to change the deeper issues of how the task runtime system actually works, and in particular, the "correct" way to execute an asynchronous task in a synchronous context, which has multiple answers that are not wrong.
[1] The fact that async functions have a fundamentally different API and ABI makes the "what color is your function" argument somewhat specious: these functions have color because they're different, and you have to be aware of the difference. Only by basically forbidding async (or, in some languages, sync) functions entirely can you make the difference appear to go away, but you'll notice that that doesn't exactly "solve" the issue.
- endorphine 5y ago> In principle, it's possible to support such a scenario by making such an await just "run" the promise to completion. But here you run into issues with how the runtime handles asynchronous tasks. Is it possible to spin a nested event loop? Not necessarily, it depends on the runtime. If your synchronous call is already on a task (so there's basically an async function on the callstack), you would really want to basically inline all of the function calls so that the await causes the same transformation were it to be called directly in that async function. But such inlining is impossible (due to dynamic function calls). I feel like this touches the point that I'm trying to grasp currently. So, do I understand correctly that in JS land, calling `await` from a non-async function and making that await block until that promise was resolved, would result in a deadlock, because JS is single-threaded? Why do we need a "nested event loop"? Is it because (a) the non-async caller blocks the only thread, and so (b) there's noone else in order to fulfill that promise being awaited on?