Re: [w3c/webcomponents] [idea] childConnectedCallback and childDisconnectedCallback (#550)

Maybe "synchronous" isn't the right term, but from my experience, no macro tasks fire during parsing, and no animation frames either (at least during parsing of the body, though some seem to fire during parsing of the head). So once we're in parsing of the body (including custom elements), then it is almost like "synchronous" in the sense that there's no possible way to defer until after parsing except with `setTimeout`, so `Promise.resolve()` is not useful (this is in actual practice from all that I currently observe, not from reading the spec).

See, here's an example:

<img width="611" alt="screen shot 2019-01-27 at 9 40 03 pm" src="https://user-images.githubusercontent.com/297678/51816616-30b57c00-227c-11e9-9062-657bd8a8c5d2.png">

(Note I am hovering on `this.children.length` and it is `0` although it does have children.

Thus because it is impossible to use microtask-based deferral to wait for children to be ready, and because `MutationObserver` _will not fire during parsing_, and because animation frames do not fire during this process, we have _no choice_ but to use a macrotask (`setTimeout`) in order to defer to a future point in time when children will be ready.

And as an ugly side effect of this, anything in a `<script>` tag after the custom elements will have been evaluated _**before**_ the next macrotask that we deferred to, thus the script-tag code also needs to use a macrotask deferral to fire code after children have been handled.

After doing the macrotask deferral in the custom element code, we then need to manually handle children once (because remember `MutationObserver` does not fire during parsing, and will never fire after parsing unless children have changed, which in most cases they haven't and we need to handle the existing children).

So, because of this, parsing is effectively "synchronous": `connectedCallback`s all fire in preorder tree order during parsing "synchronously" because _**even the custom element author can not use microtask deferral**_ to handle children, only macrotask deferral.

Macrotasks don't fire until after parsing (from my observations). So, you see:

<img width="628" alt="screen shot 2019-01-27 at 10 05 00 pm" src="https://user-images.githubusercontent.com/297678/51817461-b1c24280-227f-11e9-9a0d-7253bde3a0c6.png">

This is a huge pain.

---

In my custom elements, APIs and state are ready after both `connectedCallback`s and `childConnectedCallback`s have fired, only `childConnectedCallback` being reliable because at that point `parentElement` and `children` are both upgraded and thus parent and children can rely on each other's APIs.

So if end users of the APIs need to reply on APIs and state that only exist after both `connectedCallback`s and `childConnectedCallback`s of the custom elements have fired, then they need to introduce an unintuitive `setTimeout` call in their own code.

Do you see the problem now?

---

For example, suppose we have elements that create state in both `connectedCallback` and `childConnectedCallback` below. Then if initial calls to `childConnectedCallback` are async (due to using macrotask deferral and considering that `MutationObserver` is not an option during parsing), then we have to do the following to access all state properly as an end user of the elements:

```html
<some-element>
  <other-element>
  </other-element>
</some-element>
<script>
  // `childConnectedCallback` has not fired yet. And guess what: `connectedCallback` has
  // to defer in order to manually fire `childConnectedCallback` in a future macrotask,
  // because guess what: `MutationObserver` does NOT fire on initially connected children!
  // So guess even more what! Because the elements must defer with a macrotask, then we
  // (speaking as the end user of the custom elements) need to to defer with a macrotask:

  const el = document.querySelector('some-element')

  console.log(el.someStateCreatedInChildConnectedCallback) // undefined

  Promise.resolve().then(() => {
    console.log(el.someStateCreatedInChildConnectedCallback) // STILL undefined, WAT?
  })

  setTimeout(() => {
    console.log(el.someStateCreatedInChildConnectedCallback) // FINALLY! Ugh.
  })
</script>
```

---

It's plain and simple: It'd be great to have a "synchronous" (in the sense I described above with regard to parsing) `childConnectedCallback`.

An easy way to implement it is with the monkey patching I [linked to](https://github.com/w3c/webcomponents/issues/785), or perhaps a class-factory mixin.

-- 
You are receiving this because you are subscribed to this thread.
Reply to this email directly or view it on GitHub:
https://github.com/w3c/webcomponents/issues/550#issuecomment-458010173

Received on Monday, 28 January 2019 06:15:07 UTC