#294: TAG Review Request: queueMicrotask

Visit on Github.

Opened Jul 20, 2018

Hello TAG!

I'm requesting a TAG review of:

Further details (optional):

We'd prefer the TAG provide feedback as (please select one):

  • open issues in our Github repo for each point of feedback
  • open a single issue in our Github repo for the entire review
  • [x ] leave review feedback as a comment in this issue and @-notify [github usernames]

Discussions

Comment by @cynthia Jul 25, 2018 (See Github)

@fergald The explainer Google Doc link seems to be locked, would it be possible to make it world open?

Comment by @travisleithead Jul 25, 2018 (See Github)

@dbaron wonders if the name "microtask" is the best name to expose to developers--yes its the spec-term, but may not be the best name :-)

Comment by @domenic Jul 25, 2018 (See Github)

Hmm, any other suggestions?

Comment by @fergald Jul 26, 2018 (See Github)

@cynthia Sorry about that. Done.

Comment by @dbaron Jul 26, 2018 (See Github)

So one thought is that setTimeout has clearTimeout, requestAnimationFrame has cancelAnimationFrame, and requestIdleCallback has cancelIdleCallback... but this has no cancellation method, which seems a bit odd. (It could be problematic if somebody is trying to convert code that uses one of the others to this one, which seems like a pretty reasonable scenario.)

Comment by @slightlyoff Jul 26, 2018 (See Github)

Hey @fergald: thanks for filing a review request!

Some questions:

  • Is there a fuller code example somewhere? How would a developer use this API in context vs. the alternatives?
  • Is there an argument against Promise.resolve().then(....)? Implicit object allocation? Promise resolution order? Something else? The original discussion thread takes a lot of turns and I'm struggling to pull out the case being argued here.
  • What other timings should be exposed that aren't available in an API like this today?
Comment by @domenic Jul 26, 2018 (See Github)

I'm working on adding some examples to the spec in my spare time; I have them about half-completed.

The argument against Promise.resolve().then() is severalfold:

  • Object allocation
  • Swallows exceptions and converts them into rejected promises
  • Bad layering: promises are layered on top of microtasks, but right now you need to use the high-level feature (promises) to get at the lower-level underlying capability (microtasks). We should just expose the lower-level feature.

Not aware of many other unavailable timings, personally... maybe the different task sources the browser uses for task-queuing?

Comment by @fergald Jul 27, 2018 (See Github)

@slightlyoff

Re: Arguments - I've updated the explainer to include Domenic's justifications.

Re: example for using this vs alternatives - I've rewritten the motivation section. There are 2 things I could be explaining:

  • when to use microtask vs task vs RAF
  • why we want to make queueMicrotask an API

Previously the doc was a mix of both. It's now focused on the latter (but includes a link to another article that explains more about the former). Viewed in that way, there are no reasonable alternatives, if you want to queue a microtask you should call queueMicrotask(), there is no scenario where you should still consider Promise.resolve().then() (assuming you're on a browser that supports it).

If you feel strongly that the doc needs to explain more about what a microtask is and when it's the right choice, I can add that but it seems secondary.

Re: other timings that should be exposed - I defer to others on that.

@dbaron Cancellation seems reasonable to me. @domenic any opinions on that?

Comment by @slightlyoff Jul 27, 2018 (See Github)

The last argument about layering is pretty compelling to me. Do we know for a fact that implementations don't elide Promise.resolve() overhead?

Comment by @domenic Jul 27, 2018 (See Github)

I'm like 95% sure without testing/asking anyone. There are so many observable side effects to the promise existing (especially if the queued code returns anything or throws an exception) that it'd take some serious engine heroics to elide the result, and even if they did you could easily fall off of the garden path and accidentally force it to be allocated again.

Comment by @domenic Aug 1, 2018 (See Github)

@dbaron Cancellation seems reasonable to me. @domenic any opinions on that?

Fergal and I discussed this offline. We were initially agnostic, but came around eventually to thinking that cancelation isn't desirable, at least for now. The main reasons are:

  • It isn't part of the base primitive in existing spec infrastructure, and probably not in implementations either. Promise and mutation observer microtasks are never canceled. As this API is in the spirit of exposing primitives, we shouldn't add on top of it.
  • It isn't nearly as necessary for queueMicrotask as it is for the other scheduling APIs, because microtasks are basically-synchronous, so the intervening time you'd have to cancel is small. In other words, it's much harder to get new information that would cause you to cancel during the rest of your synchronous turn, than it is when multiple event loop turns could happen. For example it would be impossible for a user to click a "Cancel" button that triggers an event handler to cancel the microtask; the event handler would always fire after the microtask already completed.
  • If users really want this, they can build it themselves, by doing queueMicrotask(() => { if (canceled) { return; } ... }). Unlike similar code with setTimeout(), requestAnimationFrame(), et al., asking the browser to hold on to a now-redundant closure is not a big deal, because it's such a short time period.
  • There's no precedent in user-space libraries that attempt to give microtask semantics, even though such libraries could emulate cancelation by doing if (canceled) { ... } wrapping.
Comment by @domenic Aug 1, 2018 (See Github)

Pull request for examples up: https://github.com/whatwg/html/pull/3873

Comment by @dbaron Aug 10, 2018 (See Github)

Your conclusion about cancellation seems fine with me.

Discussed Aug 21, 2018 (See Github)

TL: I'm pretty satisfied with how this has shaped up.

PL: Do we want to raise anything else ?

TL: Don't think so.

Comment by @travisleithead Aug 21, 2018 (See Github)

I'm also satisfied with the conclusions. I only have a couple of minor bits of feedback:

  1. The explainer notes at the beginning, that one of the ways developers workaround the lack of queueMicrotask is by (3) triggering custom element reactions. However, according to the HTML spec, custom element reactions do not actually use microtasks (they use the special "almost synchronous" nano-task concept). So, I'd like to see that assertion dropped from the explainer. I don't believe that we should consider a queueNanotask because such an API would invoke its callback nearly synchronously, which is really pretty pointless--rather the API would need to bind to some other action to be more useful, and at that point it's starting to look a lot like Object.observe.

  2. It would be nice to move the explainer into GitHub (WICG) and reformat as an HTML or MD file for improved discoverability.

Comment by @fergald Sep 4, 2018 (See Github)

@travisleithead Thanks for comments. Removed the custom elements line and migrated the doc to

https://fergald.github.io/docs/explainers/queueMicrotask.html

Comment by @travisleithead Sep 4, 2018 (See Github)

@fergald Thanks!