WICG / local-font-access

Web API for enumerating fonts on the local system
https://wicg.github.io/local-font-access
Apache License 2.0
77 stars 16 forks source link

The async iteration definition isn't very async #51

Closed jakearchibald closed 3 years ago

jakearchibald commented 3 years ago

Right now it gathers up all the font representations in the initialisation steps. It does this on the main thread, which suggests sync I/O, which I don't think is the intention.

But also, gathering everything up front isn't really how async iteration works. If it's ok to gather everything up front, this should just be a sequence behind a promise.

However, if returning everything at once is likely to be slow, then maybe there's a benefit to returning them one by one, so UI can update progressively. This might depend on how much up-front work is required to do the sorting vs computing the metadata. It'd be good to do some science here to influence the decision.

If this becomes a true async iterator, it raises some questions that need to be answered in the prose:

// User has fonts a, c, and e.
const iterator = navigator.fonts.query();
const font1 = await iterator.next();
// font1 is a.
// But then, the user deletes font c.
const font2 = await iterator.next();
// What is font2?

And:

// User has fonts a, c, and e.
const iterator = navigator.fonts.query();
const font1 = await iterator.next();
// font1 is a.
// But then, the user installs font b.
const font2 = await iterator.next();
// What is font2?

Additionally, a true async iterator should probably check for permission in "get the next iteration result".

jakearchibald commented 3 years ago

Ah, I see there's note for some of this:

User agents are expected to actually populate the iterator’s queue asynchronously and possibly lazily, although this is not observable.

Although it is observable, as impacts the two examples above.

jakearchibald commented 3 years ago

Looking at the spec a bit closer, it seems like creating a sortable name is pretty much all of the work needed to get the metadata, no?

domenic commented 3 years ago

If this becomes a true async iterator, it raises some questions that need to be answered in the prose:

Note that to answer these in the spec, you'll want to do it within the context of the "get the next iteration result" algorithm.

oyiptong commented 3 years ago

Right now it gathers up all the font representations in the initialisation steps. It does this on the main thread, which suggests sync I/O, which I don't think is the intention.

The implementation at the moment does not do this on the main thread. The list is gathered up on the first iteration.

Additionally, a true async iterator should probably check for permission in "get the next iteration result". Good feedback. That's what the current implementation does.

However, if returning everything at once is likely to be slow, then maybe there's a benefit to returning them one by one, so UI can update progressively. This might depend on how much up-front work is required to do the sorting vs computing the metadata. It'd be good to do some science here to influence the decision.

Based on how the current system APIs work, obtaining the font list is expensive, but once it's obtained, the enumeration itself is cheap. The fonts are then sorted. To sort the fonts, the whole list must be obtained.

This seems to point to a promise, then sequence API shape.

Now, given that we're building for the future, in the future, the operating systems could have a font cache daemon that could store font data in, say a database-like fashion and return fonts piecemeal and sorted and returned as a stream, leading into an async iteration interface. If system APIs change, the async iterators could be a great performance advantage.

I've tested the performance of async iterators and they don't cost a lot more: https://colab.research.google.com/drive/1C59LKSPY6ksZorcuLjrs40uABW1jX1Jn?usp=sharing

Should the API shape match the current system API shape, or should we build the API shape that sets us up to be the most efficient in the future?

jakearchibald commented 3 years ago

Now, given that we're building for the future, in the future, the operating systems could have a font cache daemon that could store font data in, say a database-like fashion and return fonts piecemeal and sorted and returned as a stream, leading into an async iteration interface. If system APIs change, the async iterators could be a great performance advantage.

Is there any indication that this might happen in the foreseeable future?

If not, we always add another method that returns an iterator later.

annevk commented 3 years ago

When would developers want one-by-one results? Would that actually help applications in some way in "the future"? If there already are uses cases for getting all of them (and it seems to me you want to filter) I tend to agree with Jake on where to start.

jakearchibald commented 3 years ago

If you wanted to provide a list of fonts the user has, and getting all the results takes 5 seconds, but getting the first result takes 100ms, then streaming them in would be nice. But since all the effort is up-front right now, we're kinda imagining use-cases.

annevk commented 3 years ago

Also, if a browser wants to provide agency to the user over which fonts to expose one-by-one wouldn't help either.

jakearchibald commented 3 years ago

Yeah, although we may in future want to add things to the data object that involve more processing than the naming details (which is all that's currently there). Eg, we might want to provide some layout metrics.

I guess the easiest way we could do that is by adding a data.getExtendedData() method which returns a promise. That's in-keeping with what we do to get the font bytes.

So, yeah, I'm more and more convinced this should be a sequence behind a promise.

inexorabletash commented 3 years ago

The API now asynchronously returns a plain array, so the iteration is sync.