The fundamental problem with top-level await is that it prevents two unrelated modules doing asynchronous work in parallel, because module evaluation order is strictly deterministic.
What if we provided an escape valve that relaxed that constraint?
// main.js
import foo from async './foo.js';
import bar from async './bar.js';
import baz from './baz.js';
console.log(foo, bar, baz);
// foo.js
export default await fetch('/foo.json').then(r => r.json());
console.log('got foo');
// bar.js
export default await fetch('/bar.json').then(r => r.json());
console.log('got bar');
export default 'BAZ';
console.log('got baz');
By using from async
, we're allowing foo.js
and bar.js
to use top-level await
. (If a consumer were to just use from
, it would be an error.)
The output of this (assuming foo.json contains "FOO"
and bar.json contains "BAR"
) could be either of these:
BAZ
FOO
BAR
FOO BAR BAZ
BAZ
BAR
FOO
FOO BAR BAZ
This is roughly analogous to wrapping foo.js
and bar.js
in async functions, except that main.js
awaits both of them:
async function foo() {
const result = await fetch('/foo.json').then(r => r.json());
console.log('got foo');
return result;
}
async function bar() {
const result = await fetch('/bar.json').then(r => r.json());
console.log('got bar');
return result;
}
function baz() {
const result = 'BAZ';
console.log('got baz');
return result;
}
function main() {
Promise.all([foo(), bar(), baz()]).then(([foo, bar, baz]) => {
console.log(foo, bar, baz);
});
}
I've argued elsewhere that determinism is important, but maybe if we can opt-in to non-determinism with our eyes open, it's okay?
I don't see why not. The concerns I and others have expressed are all about nested (unrelated) modules blocking each other, which doesn't apply to entry modules. Hell, I wrote a thing (lit-node) that allows you to use TLA in entry modules, because it's incredibly useful in a scripting context with none of the drawbacks.
If I use
await
in a non-async function I get this:I'm envisaging something along those lines — 'await is only valid syntax if imported using
import ... from async 'specifier'
or dynamicimport()
'. Wordy but you get the idea. I'm sure there are lots of corner cases to consider here.Definitely. I've argued that it's a huge problem. But if you're aware of possible race conditions — and the explicit
from async
is meant to enforce that awareness — you can deal with them. Basically I think it boils down to 'make damn sure you're explicit about your dependencies', i.e. don't depend on a polyfill that may or may not exist yet, and avoiding circular deps