-
Notifications
You must be signed in to change notification settings - Fork 46
Conversation
This patch updates the explainer to be in terms of requiring modules containing top-level await to be imported with `import await`. Spec text not yet written.
Thanks to @sokra and everyone at the JSKongress 2019 Deep Track for developing this idea! |
`async` functions and top-level `await` can be thought of as being "viral" because they have to be considered all the way up the call stack/dependency chain. You can't just change an ordinary function to an async function without considering its users, who would probably be broken, unless they happen to always `await` the result. It would be quite unusual for them to do such defensive `await`-ing, and we don't seem to see that catching on as an idiom. | ||
|
||
The same applies to top-level `await`--their dependencies need to `await` the module when importing, and recursively so. This means that, just like changing a function into an `async` function, it's a semver-major change to add top-level `await` to a module. We will need to document this effect carefully, to avoid potential ecosystem breakage. | ||
|
||
## History |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between import await "module"
and await import("module")
?
import await
starts fetching during the Fetch and Parse
phase, while await import()
starts fetching during the Evaluation
phase.
Multiple import await
will run in parallel, while multiple await import()
will run sequential.
Exports of import await
can be analysed statically and incorrect export names will result in a SyntaxError. Analyzing await import()
is harder and incorrect export names will result in undefined
and a RuntimeError.
import await
is hoisted, await import()
is an Expression and not hoisted.
import await
can only be placed on top-level, while await import()
can be placed at top-level or in async functions at any location where an Expression is allowed.
import await
must be used with a String Literal, while await import()
can be used with any expression as argument (await import(`./data/${value}.js`)
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for writing this! I added it to the FAQ.
I like the general direction of this change, but I’m concerned about import await being viral. It’d be great if the only thing that makes a module async is adding TLA. |
I would be opposed to the proposal moving forward with this change. By making callers need to know whether they are importing a module which awaits or not, this essentially makes the proposal sugar for the To be clearer about why it's important for callers to be agnostic, one of the major use cases of top-level await is maintaining the module abstraction boundary while allowing the depended-upon module to add asynchronous initialization steps. @wycats has made this point in previous meetings. Here is an example. Consider a module like so: const template = `<p>Hello, my name is: <slot></slot></p>`;
customElements.define("name-tag", class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = template;
}
}); which is then imported via import "./name-tag.mjs"; Then the designer gets their hands on things. "This template is too simple!", they say. They beef it up, with a const template = await (await fetch(new URL("./name-tag-template.html", import.meta.url))).text();
customElements.define("name-tag", class extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' }).innerHTML = template;
}
}); The point of top-level await is that this is not a breaking change for the consumer of name-tag.mjs. Just like you are able to refactor one module into multiple modules, with top-level await, you can refactor from one module into multiple files, or in general multiple async initialization steps. With the proposal in the OP, this refactoring becomes a breaking change, for the consumers of name-tag.mjs (recursively, throughout the entire graph). One way to mitigate this would be if everyone everywhere preemptively replaced all Slowly, gradually, and with much pain, we will either switch the entire JavaScript ecosystem to using Introducing this kind of dynamic into top-level await makes the proposal unusable for me as a package author, and for the cases we are interested in in the web component ecosystem in general. |
@domenic Yes, this does add friction to the upgrade path; that's the idea of this change. There was an exact opposite concern that it would be unfortunate if a deep dependency became async, now all dependent modules are async. Seems like we just have to make a decision one way or the other on whether this transparent upgrade is desirable or not. |
@domenic @ljharb that's the idea of this change: using top-level await should be a breaking change for the module consumer. I think this is desired since async operations can take a significant amount of time. If anyone in the module graph was able to defer the application startup, it might become hard for application authors to find the cause for long startup times. Another advantage of this change is that it would not change the way how module evaluation is currently specified. Introducing TLA to the specification would be a non-breaking change. @domenic regarding your example: native HTML modules would make the module graph synchronous again.
I think, this is a valid concern (and it's probably what @ljharb meant with TLA becoming viral). But it's also very speculative and hard to predict. If common things like HTML and CSS can be imported synchronously, I don't think that this pattern will become too popular. It might be a different story with Web Assembly modules though since synchronous compilation is discouraged by some engine implementers. If WASM modules become popular in the JS ecosystem, However, I still like that the application developer is in control to decide whether to |
@jhnns i have no issue with adding TLA being a breaking change; my issue is with adding |
@jhnns HTML modules will be async, not sync. |
@domenic Good to know that Wasm modules aren't the only one. I can see how it's not so nice that you have to update all the Do you have any references with more information about HTML modules being async? Maybe we could point to them from the presentation and the explainer here so people can learn more context. |
The intention of This viral behavior is intentional and will make developers aware bad practices. For web applications you should avoid that your application/entry point becomes an async module, even when other async modules like WASM are used. When your application becomes an async module this probably means the user will see a white screen until all async modules are resolved (i. e. until WASM is compiled). Instead viral Note that the |
|
||
This proposal allows `await` to be used at the top-level of a module, outside of functions. | ||
|
||
This proposal adds a new type of `import` statement, which is `import await`. `import await` can be used both modules which do and don't contain a top-level `await`. However, `import` statements may not be used on modules with top-level `await`--that misuse causes an error during the Linking phase. Some forms of `import await`: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import
statement
Is ImportDeclaration
a Statement
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh, it's an explainer.
As presented in the March 2019 TC39 meeting, the champion group does not plan to take this approach, so closing the PR. |
This patch updates the explainer to be in terms of requiring modules
containing top-level await to be imported with
import await
. Spectext not yet written.