Closed littledan closed 3 years ago
Note that we already concluded the discussion about the design of top-level await when we promoted it to Stage 3. Any changes from here could be proposed for consensus, and would generally be expected to be based on implementation experience.
Interestingly an await expression can still throw synchronously, if the operand throws. I think that solves the timing issue pretty neatly.
I agree with @littledan that it should fail at parse time, not at runtime.
Sorry to be late for the discussion.
This might be already discussed, but how about, in Step 9 of https://w3c.github.io/ServiceWorker/#update-algorithm, checking script
's record's [[Async]]
and if it is true, reject the updating just like ordinal fetch errors? (i.e. replacing If the algorithm asynchronously completes with null, then:
with If the algorithm asynchronously completes with null or the asynchronous completion value's record's [[Async]] is true, then:
and removes the allowAwait argument from https://github.com/whatwg/html/pull/4352)
I'm wondering if we can remove the top-level await allowance flag from fetching algorithm, because the top-level await allowance is more like an attribute of the calling context of module fetching (i.e. disallow TLA on top-level SW script fetch, allow TLA dynamic import on SW if we would allow dynamic import on SW), rather than an attribute of global scope nor fetching itself.
@hiroshige-g yes, I believe that would work. However, it would mean that we keep fetching and parsing any dependencies after the async module. However, since we're going to fail the service worker anyway, that probably isn't an issue. I'll write up some spec text tomorrow.
This was somewhat more difficult than anticipated, because we all forgot that [[Async]]
doesn't work like that; it's only true if the module itself is async, not if it has async dependencies. I pushed https://github.com/w3c/ServiceWorker/pull/1444/commits/8d7ba391a070baf3cd84b2dae9391f1cfd03e533.
I saw sync XHR mentioned earlier, so I have a question: is it also planned to ban sync XMLHttpRequest
and sync new WebAssembly.Module(foo)
/ new WebAssembly.Instance(foo, bar)
in service workers? Should I file a new issue for that?
XMLHttpRequest
is already unavailable in a service worker.
There are no plans to remove those web assembly modules. If you think things should change here, can you explain why?
@jakearchibald I assume the reason sync XHR was removed is because it blocks the event loop, but new WebAssembly.Module
/ new WebAssembly.Instance
are synchronous, so they also block the event loop, which is why they have a 4 KB limit on the main thread. So will they also have a 4 KB limit in service workers, or is that limit removed?
The intent here is to prevent footguns. We aren't blocking while (true)
, but we are blocking await
since its performance impact isn't always clear at dev time.
they have a 4 KB limit on the main thread. So will they also have a 4 KB limit in service workers, or is that limit removed?
That's definitely worth discussing! Could you file an issue?
@jakearchibald Ah, okay, thanks for clarifying! I've opened a new issue here: https://github.com/w3c/ServiceWorker/issues/1499
With this pull being merged, currently there is no way to import a module that uses TLA whatsoever.
I understand the reason is that import()
invokes fetch and so doesn't work offline, but could we get some way to prefetch/parse modules so that the service worker can still asynchronously evaluate them? Which is to say, we declare them upfront synchronously and only those modules can be dynamically imported later:
// Service worker can treat ./moduleWithTLA.js and it's subgraph as necessary for
// the service worker, so it should fetch this graph as well, the service worker would
// only become active once the graph for ./moduleWithTLA.js has successfully fetched and
// parsed, it doesn't however actually block evaluation of the current module, once this
// service worker is active this is even simply a no-op
registerModule("./moduleWithTLA.js");
self.addEventListener("some-event", async (evt) => {
evt.waitUntil(async () => {
// Allowed, triggers no fetch as once this service worker is active, ./moduleWithTLA.js
// is already saved and cached with the rest of the modules
await import("./moduleWithTLA.js");
// Not allowed, this module wasn't registered, so it wasn't fetched before activating
// the service worker, as such `import()` immediately throws an error
await import("./notRegistered.js");
});
});
Actually I realised the above could be accomplished with the module blocks proposal, e.g. the service worker would not become active until all imports in all module blocks had also fetched. Then import()
would only accept module blocks e.g.:
// We can dynamically import module blocks only as they are statically declared so
// the service worker can discover them the same way it does with regular modules
const moduleWithTLA = module {
// Before becoming the active service worker, this graph must successfully fetch
// and parse
export * from "./moduleWithTLA.js";
}
self.addEventListener("some-event", async (evt) => {
evt.waitUntil(async () => {
// Is a module block so allowed
const { someExport } = await import(moduleWithTLA);
// Is a string so not allowed
await import("some-string");
});
});
I think the best solution here is to figure out how import()
should work in a service worker. That fixes both problems.
The top-level await proposal would make module evaluation result in a Promise, rather than synchronous success or failure. This would affect the Update algorithm, which takes the steps in 16.1 if there is such a failure.
Does anyone have such concerns about this evaluation being asynchronous?
If this seems alright, then I'll make sure we have a PR for ServiceWorker after top-level await reaches Stage 3. It looks like there would be a little bit of refactoring on the SW side, so I'll wait for things to settle down editorially on the TC39 side before making more churn with dependent specs.
cc @domenic @guybedford @mylesborins