tc39 / ecma402

Status, process, and documents for ECMA 402
https://tc39.es/ecma402/
Other
534 stars 104 forks source link

Async APIs for Intl loading #434

Open littledan opened 4 years ago

littledan commented 4 years ago

There's a lot of data in CLDR (or whatever other locale data an Intl implementation uses), so much that it doesn't work in practice to ship it all with JS implementations. For example:

Could we address some of this by creating async APIs which have the chance to fetch the relevant data, in a way provided by the engine? As a straw-person, each Intl constructor could have a create static method, which returns a Promise, which resolves with the instance, based on a host hook to fetch the data. Usage could look like this:

let displayNames = await Intl.DisplayNames.create("fr", {type: "emoji-keywords"})
displayNames.of("🦻")  // ["accessibilité", "malentendant", "oreille appareillée", "prothèse auditive"]

One risk is that async APIs would be open to timing attacks: if the data is cached locally, you could see whether someone else has requested this locale data by checking how long it takes for this API to return. Browsers have adopted "double-keyed caching" to cache based on not just the URL of the resource but also the origin which referred it. However, if the fetch is going to a browser-provided resource, I don't know whether this would reveal significantly interesting information to constitute a meaningful attack. We should check with a security expert (which I am not) before proceeding, to be sure.

sffc commented 4 years ago

I definitely agree with this direction. This issue has a lot of overlap with #210, but is more specific: async APIs is one piece that will be required before moving onto the full-stack data loading problem.

The model of adding an async method like .create() to Intl objects is good. Since all of the settings are available in the constructor, this operation will be able to load all required data for that specific operation (for example, only load time zone names if the skeleton requires it).

A small number of other Intl functions, like Intl.Locale.prototype.maximize, may also want to be async, unless we want to pre-load the likely subtags in the constructor just in case the programmer wants to use them later.

sffc commented 4 years ago

Related: https://github.com/tc39/proposal-async-init

littledan commented 4 years ago

I think async constructors are a cool idea. At the same time, I think this would work in Intl just fine as a static async factor method, if async constructors aren't a thing when we want to move this to Stage 3.

littledan commented 4 years ago

It's interesting to mention #210. I guess I was imagining these on separate axes--that if you use the data-driven API, you'd provide that data synchronously to the constructor. At the same time, practically speaking, you may fetch that data asynchronously, so they might end up looking analogous to an application developer using a framework which provides that data.

Nit: I'd like to stick with the pattern of making each method either always-sync or always-async. So, if we want to make Intl.Locale.prototype.maximize load data sometimes, I'd suggest that the loading version be a separate method name. Either way, we have to solve a lot of the same problems between async instance methods and async constructors/factory functions, e.g., developing a model we're OK with for caching, where the data comes from, etc.

eemeli commented 4 years ago

A while ago, I put together an idea of a proposal for this, but never really took it anywhere: https://github.com/eemeli/proposal-intl-loadlocales

In other words, a big +1 on this.