2

Chrome mobile.

I have observed a bit confusing behavior when upgrading element which's constructor (or field initializer) throws an exception.

const testline=document.querySelector("#testline");
const testextc=document.querySelector("#testextc");
logtest=function(s) {testline.innerText+=s;};
//function unknown() {throws when called};

const ExtC=class extends HTMLElement {
    constructor() {
        logtest(" C.c-");
        //unknown(); //exception before super
        super();
        logtest(" C.c+");
        //unknown(); //exception after super
        };
    c_field=(function() {
        logtest(" C.f");
        //unknown(); //exception in field init
        return 1;
        })();
    connectedCallback() {logtest(" C.>");};
    hasChildNodes() {
        logtest(" hasChildNodes()-call");
        return super.hasChildNodes();
        }
    get [Symbol.toStringTag]() {
        return this.constructor.name;
        };
    };

logtest(" before def: "+testextc);
try {customElements.define("test-ext-c", ExtC);}
    catch(e) {logtest(" def ex="+e);};
logtest(" after def: "+testextc);
try {testextc.hasChildNodes();}
    catch(e) {logtest(" hasChildNodes() failed: "+e);};
<div id="testline"></div>
<test-ext-c id="testextc">test-ext-c</test-ext-c>

JSFiddle: https://jsfiddle.net/3z65tabc/1/

Without constructor throws, it says:

before def: [object HTMLElement] C.c- C.f C.c+ C.> after def: [object ExtC] hasChildNodes()-call

It does mean: the element is constructed, upgraded, re-connected-(life cycle callback works) and has overriden method.

When constructor throws before super(), it says:

before def: [object HTMLElement] C.c- after def: [object HTMLElement]

So the element is not constructed and not upgraded, life cycle callbacks as well as overriden method are not present. It seems OK (the specification doesn'the allow commands before super() anyway). I can check this happening by looking at element's .constructor.name.

But when the constructor throws after super(), or the field initializer throws, it says:

before def: [object HTMLElement] C.c- C.f C.c+ after def: [object ExtC] hasChildNodes()-call

or

before def: [object HTMLElement] C.c- C.f after def: [object ExtC] hasChildNodes()-call

respectively. This does mean, the element is (partially) constructed, is upgraded, the overriden method works, apparently is re-connected, but the life cycle callback is not fired.

Briefly reading the upgrade-process in the specification, this behavior seems to be correct, but why is this?

I cannot detect this looking at .constructor.name. The define method doesn't throw. How can I detect this exception have been thrown?

I have spent hours looking for why my lifecycle callbacks are not fired, until I happened to notice the mistake throwing an exception on the place, where I would not look for it as a possible reason.

P.S. My problem is, when the exception thrown is not (yet) apparent in your code's behavior, everything looks working OK except the lifecycle callbacks don't fire for unknown reason.

The specification says, that these exceptions should be re-thrown, perhaps they propagate through new and/or createElement() (have not checked), but the automatic upgrade process is not invoked directly by code, so the re-thrown exception has nowhere to propagate and so it is lost.

One more general question: how can a (partially constructed) element (object) even be instantiated with its methods etc. (and reference to this instance be stored somewhere), when it's constructor throws an exception? Is it some internal magic or can it be done using JavaScript as well? How? new will not evaluate due to exception...

Raymond Chen
  • 44,448
  • 11
  • 96
  • 135
Murphy
  • 19
  • 3

0 Answers0