Skip to content

Use fetch() for loading packages & shared libraries #22002

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

Closed
wants to merge 39 commits into from

Conversation

seanmorris
Copy link
Contributor

@seanmorris seanmorris commented May 26, 2024

This PR switches AJAX requests for file packages & shared libraries to be loaded with fetch() rather than XmlHttpRequest(). This allows service workers to make AJAX requests, since XmlHttpRequest is not available in service workers.

Previously, --embed-file was necessary to load file packages, which embeds the files into the binary and precludes caching and re-use of the package. Loading shared libraries is not possible in a service worker without this patch.

Fixes #22003

Copy link
Collaborator

@sbc100 sbc100 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we need a new option at all here or if we should just do this in all cases?

fetch(url)
.then(response => {
if(response.ok) {
return response.arrayBuffer();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not call onload here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

response.arrayBuffer(); returns a promise that resolves to a buffer, not a literal buffer.

if(response.ok) {
return response.arrayBuffer();
}
throw new Error(response.statusText + ' : ' + response.url);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this not call onerror instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me check the make sure the error has the correct stack if its not actually thrown, and then I can change it.

Copy link
Contributor Author

@seanmorris seanmorris May 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remembered why I used this pattern as I was editing: The catch() at the end of the function will catch errors coming from the fetch(), but fetch() won't throw errors by itself if the request fails, i.e. in a 404 or 500 error. For that we have to throw our own error. When we do that, we skip the then() on line 32 and head right to catch().

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about return new Promise.reject(..)

Then I think maybe you can do .then(onload, onerror) without the extra catch? Not sure which is more idomatic.

@seanmorris
Copy link
Contributor Author

I wonder if we need a new option at all here or if we should just do this in all cases?

I'd like this to be enabled by default, but I don't want to introduce any surprises in cases where people are polyfilling/overriding XmlHttpRequest. Perhaps we can enable it by default in the next minor release, and eventually deprecate the use of XmlHttpRequest gracefully.

@sbc100
Copy link
Collaborator

sbc100 commented May 28, 2024

I wonder if we need a new option at all here or if we should just do this in all cases?

I'd like this to be enabled by default, but I don't want to introduce any surprises in cases where people are polyfilling/overriding XmlHttpRequest. Perhaps we can enable it by default in the next minor release, and eventually deprecate the use of XmlHttpRequest gracefully.

We will also likely need to write a minimal polyfil for cases where we target older browser. (i.e. -sLEGACY_BROWSER_SUPPORT). See the existing polyfills in src/polyfill.

@sbc100
Copy link
Collaborator

sbc100 commented May 28, 2024

I wonder if we need a new option at all here or if we should just do this in all cases?

I'd like this to be enabled by default, but I don't want to introduce any surprises in cases where people are polyfilling/overriding XmlHttpRequest. Perhaps we can enable it by default in the next minor release, and eventually deprecate the use of XmlHttpRequest gracefully.

Polyfilling/overriding XMLHttpRequest is not something that I know of anyone doing and we don't document emscripten as supporting this. I think its reasonable to simply make the switch and add a ChangeLog entry for this.. if somebody really is overriding XMLHttpRequest it should be easy enough to also override fetch. That was we can avoid adding a new setting (each time we do this it increases our testing burden).

@seanmorris
Copy link
Contributor Author

I wonder if we need a new option at all here or if we should just do this in all cases?

I'd like this to be enabled by default, but I don't want to introduce any surprises in cases where people are polyfilling/overriding XmlHttpRequest. Perhaps we can enable it by default in the next minor release, and eventually deprecate the use of XmlHttpRequest gracefully.

Polyfilling/overriding XMLHttpRequest is not something that I know of anyone doing and we don't document emscripten as supporting this. I think its reasonable to simply make the switch and add a ChangeLog entry for this.. if somebody really is overriding XMLHttpRequest it should be easy enough to also override fetch. That was we can avoid adding a new setting (each time we do this it increases our testing burden).

I'll go ahead and make those changes.

@seanmorris seanmorris changed the title Adding -sUSE_FETCH for loading packages & shared libraries in service workers Use fetch for loading packages & shared libraries May 29, 2024
@seanmorris seanmorris changed the title Use fetch for loading packages & shared libraries Use fetch() for loading packages & shared libraries May 29, 2024
@sbc100
Copy link
Collaborator

sbc100 commented May 29, 2024

If possible, I'd like to see the fetchAsync change land first and the file_packager change as followup.

The fetchAsync is going to be a lot more risky, since it effects basically all users whereas the file packager change is something that only some users will be effected by.

Module.dataFileDownloads = Module.dataFileDownloads || {};
const url = packageName;
fetch(url)
.catch(error => { throw new Error(error + ' : ' + url) }) // Do this first so we don't catch errors from then()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of .catch here is it the same to write the error handler as the second argument to then()? Is one way preferable over the other?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, we need to catch() before then() so we only handle fetch errors here. If we putt this at the bottom, or in the second param of this(), then it will also act on the network errors generated by then(), and I'd need to add logic to skip certain errors.

Module.dataFileDownloads = Module.dataFileDownloads || {};
const url = packageName;
fetch(url)
.catch(error => { throw new Error(error + ' : ' + url) }) // Do this first so we don't catch errors from then()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our style uses braces around the single argument to arrow functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw isn't a function, so it needs to have braces or it counts as a syntax error. I'm switching to Promise.reject which is a function.

if(response.ok) {
return response.json();
}
throw new Error(response.statusText + ' : ' + response.url);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would return Promise.reject(new Error(response.statusText + ' : ' + response.url)) instead of throwing?

@seanmorris
Copy link
Contributor Author

seanmorris commented May 30, 2024

If possible, I'd like to see the fetchAsync change land first and the file_packager change as followup.

The fetchAsync is going to be a lot more risky, since it effects basically all users whereas the file packager change is something that only some users will be effected by.

#22015 - libs
#22016 - packages

Let me know how those look.

@sbc100
Copy link
Collaborator

sbc100 commented Jun 3, 2024

Now that #22015 and #22016 contains the rest of this PR I think we can close this one?

@sbc100 sbc100 closed this Jun 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Can't preload files/shared libs in service worker due to lack of XHR [PR OPEN]
2 participants