- Asynchronicity of imported module is not visible
- Imports of async modules can have potentially performance impact and should be visible to the developer
- It's difficult to see which modules are affected by the top-level-await
- Once this is visible, developer can break the async chain with
import()
- Microticks between imports may be unexpected
- Developer don't know if modules are evaluated sync or async
- Execution order is no longer guaranteed when modules add top-level-awaits
- 2 module modes: normal/sync and async
import x, { a, b } from "m"is only allowed for normal/sync modules (will throw an early Error otherwise)import await x, { a, b } from "m"- imports sync and async modules
- execution of the remaining module body is delay until all modules imported with
import awaitare resolved - A
import awaitdoes NOT block otherimport awaits (likePromise.all) - All
import awaits are hoisted (like with normalimport). - Remaining module body is always scheduled in a new microtask when
import awaitis used, even if all imported modules are sync. (import awaiton sync modules behave likeawait Promise.resolve()) import awaitmust not appear after a top-levelawait, because this looks confusing
- Using top-level
awaitorimport awaitmakes the module an async module - WebAssembly Modules are async modules
- Using
import awaitmakes it visible that- imported module may use async operations
- the scheduling of the remaining module body is changed
- importing this module may impact performance
- Disallowing
importfor async modules makes adding asynchronicity to a module intentionally a breaking change- It's possible to use asynchronicity internally when not visible on public API.
import()will hide asynchronicity behind a Promise.
- It's possible to use asynchronicity internally when not visible on public API.
- Using
import awaitmakes it easy to see where using aimport()may be a good fix for performance problems - Using
import awaitmakes it possible to transpile top-levelawaitto non-top-levelawaitcode- The proposal can be implemented as babel-plugin to play with it
import awaitallows support for Tree Shaking and Scope Hoisting, when supported by bundlers directly.
import b from "./b.js";
import await { c1, c2 } from "./c.js";
import await "./d.js";
import "./e.js";
console.log("f", b, c1, c2);
await connect();
console.log("g");
export const h = 42;It's not exactly equal, but could be polyfilled this way.
import b from "./b.js";
import cPromise from "./c.js";
import dPromise from "./d.js";
import "./e.js";
export default Promise.all([cPromise, dPromise]).then(async ([cNs, dNs]) => {
console.log("f", b, cNs.c1, cNs.c2);
await connect();
console.log("g");
const h = 42;
return {
[Symbol.toStringTag]: "Module",
get h() {
return h;
}
};
});// a.js
import "./b.js";
import await "./c.js";
import await "./e.js";
import "./g.js";
console.log(30);
// b.js
console.log(1);
// c.js
import "./d.js";
console.log(3);
// d.js
console.log(2);
// e.js
import await "./f.js";
console.log(10);
await 1;
console.log(20);
// f.js
console.log(4);
// g.js
console.log(5)Will print 1, 2, 3, 4, 5, microtick, 10, microtick, 20, microtick, 30.
Because all normal imports (and normal imports behind import await) are evaluated early it's possible to create a new concatenated module from multiple imported modules.
The example above is equal to:
console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);
const fPromise = Promise.resolve();
const ePromise = Promise.all([fPromise]).then(async () => {
console.log(10);
await 1;
console.log(20);
});
const cPromise = Promise.resolve();
Promise.all([cPromise, ePromise]).then(() => {
console.log(30);
});// db-connection.js
await connectToDB(URL);
export const dbCall = async data => { ... }
// ...// UserApi.js
import await { dbCall } from "./db-connection.js";
export const createUser = async name => {
// ...
await dbCall({ ... });
}// Actions.js
const UserApi = import("./UserApi.js");
export const CreateUserAction = async name => {
const { createUser } = await UserApi;
await createUser(name);
};// UserComponent.js
import { CreateUserAction } from "./Actions.js";
CreateUserAction("John");import await storage from "./kv-storage.js"let storage;
try {
storage = (await import("std:kv-storage")).default;
} catch {
storage = (await import("./kv-storage-polyfill.js")).default;
}
export { storage as default };Requiring an async module will return a Promise to CommonJS code
const kvStoragePromise = require("./kv-storage.js");
const somewhere = async () => {
const { default: storage } = await kvStoragePromise;
};Top-level await is not allowed in CommonJS code.
await import { named1, named2 } from "./module.js"; // Parsing problematic
import await { named1, named2 } from "./module.js";
import { named1, named2 } await from "./module.js";
import { named1, named2 } from await "./module.js"; // Weird awaiting of stringimport await def, { named1, named2 } from "./module.js";
This looks like a really good way of doing it.
Just a note on transpilation (not the spec itself):
The Promise should probably not be default exported but exported using some key
__someTopLevelAwaitPromiseKeyso that you can't just do a normal import on the transpiled module or evenimport {then} from './topLevelAwaitModule'.