-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
provide a way to execute work after browser has consumed a response #1397
Comments
So the goal is just to avoid just interrupting the single fetch response consumption, rather than wanting something like https://w3c.github.io/requestidlecallback/ to allow multiple fetches' processing to be deferred until the ServiceWorker reaches an idle point? (Note that although https://www.chromestatus.com/features/5572795866021888 indicates public support from Mozilla for that API, the link dates to Firefox OS times and there's nothing at https://github.com/mozilla/standards-positions/issues right now so I don't know what the current Mozilla/Firefox position might be, although in principle I think we're on-board with APIs to help sites improve time-to-interactive.) |
Right, the completion work may be somewhat timing dependent so we want to avoid using a full task via |
Okay. I think it's undeniable that sites will want to/already do this and that it's better to spec this than have a cargo-culted double-then(), so sounds good for Firefox/Gecko. Spec-wise it'd be good to have an info-box that explains how this interacts with streams even though it already flows from the specs. |
Note, the double-then turned out to not even be adequate in my original case. We ended up having to use setTimeout(f,0) which was worse. |
@youennf @jakearchibald Do either of you have opinions on this proposal? |
Another thought. Maybe something like |
Feels like this is a problem worth solving. I prefer Another option is: evt.afterResponse(async () => {
await doCompletionWork(response);
}); This would call My feelings aren't particularly strong though, so if no one's excited about this I'm happy with |
I also would prefer a method like Using the word "responded" might be confusing if it's also meant to be a fallback for a response. @wanderview what exactly do you mean here? Is this meant to catch a user code bug where within the fetch event handler, they literally never invoke
|
If you want to perform some completion work but you don't call addEventListener('fetch', evt => {
evt.waitUntil(evt.responded.then(doCompletionWork));
if (ShouldIgnore(evt.request)) {
// doCompletionWork() is called after the fallback to network is started
return;
}
evt.respondWith(fancyResponseLoader(evt.request));
}); Personally it feels a bit weird to add a callback-based function like evt.waitUntil(async function() {
await doAsyncStuff();
await doMoreAsyncStuff();
await evt.responded;
return doFinalStuff();
}()); It also seems like someone could easily make their own |
I agree with that. And I would be fine with If we do want to better support the case where |
The use case makes sense to me too. respondWith is calling waitUntil under the hood so that waitUntil for fetch events is probably not used much. Also, the tasks done in doCompletionWork are probably often fast. The need to call waitUntil will not be as evident as for other service worker events so it seems best to automate it. As of the exact details, listing some existing/envisioned use cases might help. Some related questions:
|
I don't think the statement "waitUntil for fetch events is probably not used much" is necessarily true. The sites I have looked at lately do use FetchEvent.waitUntil() for things that may last beyond the respondWith() resolution. I agree that waitUntil is a repeated issue that needs educating people, but I don't think we should give up on the extensible web and stop exposing primitives in favor of wrappers because of it. I feel we should expose the attribute promise even if we also expose a wrapper helper. Would people be ok with both
I think this has been our assumption, but I've seen tracing that says there is a small significant drag from even simple code (at least in chrome). For example, a function that recorded timing measurements in a js object and then issued a fetch() to log the result to the server added a 5ms to 10ms drag on FetchEvents. Looking at the completion work code you would not expect it to really be noticeable, but it was. Also, this code can be a further drag right after the worker thread starts up and the js jit is cold, etc. That often means the navigation FetchEvent is going to be the slowest which is one we really care about.
I think we could have the responded promise resolve with the consumed Response. If they want an unconsumed Response the SW script would have to do their own clone() and pass the duplicate into their completion work handler via a closure.
I think it would be useful for the promise to resolve whether a promise settles with a promise to respondWith() or respondWith() is not called triggering fallback. Of course whether those are treated the same by the site is up to how they write their script.
I think it should be called for fallback cases as well. If sites don't want that they can avoid registering their completion work in those cases. Edit: I guess if everyone else wants an |
I agree. That said, both afterResponse and responded names seem to convey that this only happens in case the fetch event is responded. Your initial proposed name 'complete' seems somehow more accurate since we are talking of completion of a fetch task and completion handlers. With regards to responded vs. afterResponse, I am not intimate enough with service worker scripts to have strong feelings either way.
The completion handler might take benefit of knowing whether:
|
Other naming ideas:
If we fulfill the promise with the consumed response then they can tell if respondWith() was called or not. I don't think we can differentiate between a direct Response being passed vs a Promise-to-Response. I believe WebIDL automatically coerces the direct Response to a Promise-to-Response.
If we expose a promise attribute this can be exposed as a promise rejection. If we did a callback function like |
handled has the advantage to relate to the "Handle Fetch" algorithm.
Agreed and I do not see a need to separate this case in smaller cases. |
Pre TPAC notes:
|
Resolution:
|
@wanderview would using |
It depends on the site, but in the cases I'm thinking of probably not. Its very likely you would need more of a delay to wait for the current page to finish loading. One site I know of uses a 10 second timeout before writing to cache. You could also envisage something that keeps the responses in memory in the SW and then the page postMessages the SW to persist them after page load is complete. Edit: The real answer is sites need to measure with real data to determine if this is a problem at all and then the best mitigation. |
As FetchEvent.handled is implemented in the spec, can we close this issue? |
Agreed! |
Recently I've been looking at some code that does something like this:
After it has a Response it does some amount of completion work. This could book keeping, recording metrics, opportunistic caching, etc.
What I have seen in these cases is that the completion work ends up delaying the browser from processing the response.
It might be nice to provide some way for the code to execute something after the browser has processed the response. A couple options:
respondWith()
return a promise for once it has completed its work on the service worker thread.FetchEvent.complete
promise for once it has completed its work on the SW thread. This could also support fallback cases where respondWith() is not called at all.With something like this we would rewrite the example above as:
The work around for this issue is to try to use a microtask or task to get your completion work to execute after the browser is done. This is pretty much guesswork, though, since I don't think we clearly define how browsers process the response on the SW thread. For example, in chrome we end up internally queuing a microtask after the respondWith promise completes. So if you want to run your completion work after you need two microtasks to get behind the browser. And of course other browsers might be different. Providing an explicit API would avoid this sort of confusion.
The text was updated successfully, but these errors were encountered: