6

Catching errors thrown from connectedCallback

 11 months ago
source link: https://nolanlawson.com/2023/08/25/catching-errors-thrown-from-connectedcallback/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Catching errors thrown from connectedCallback

Posted August 25, 2023 by Nolan Lawson in Web, web components. Leave a Comment

Here’s a deep-in-the-weeds thing about web components that I ran into recently.

Let’s say you have a humble component:

class Hello extends HTMLElement {}
customElements.define('hello-world', Hello);

And let’s say that this component throws an error in its connectedCallback:

class Hello extends HTMLElement {
connectedCallback() {
throw new Error('haha!');
}
}

Why would it do that? I dunno, maybe it needs to validate its props or something. Or maybe it’s just having a bad day.

In any case, you might wonder: how could you test this functionality? You might naïvely try a try/catch:

const element = document.createElement('hello-world');
try {
document.body.appendChild(element);
} catch (error) {
console.log('Caught?', error);
}

Unfortunately, this doesn’t work:

In the DevTools console, you’ll see:

Uncaught Error: haha!

Our elusive error is uncaught. So… how can you catch it? In the end, it’s fairly simple:

window.addEventListener('error', event => {
console.log('Caught!', event.error);
});
document.body.appendChild(element);

This will actually catch the error:

As it turns out, connectedCallback errors bubble up directly to the window, rather than locally to where you called appendChild. (Even though appendChild is what caused connectedCallback to fire in the first place. For the spec nerds out there, this is apparently called a custom element callback reaction.)

Our addEventListener solution works, but it’s a little janky and error-prone. In short:

  • You need to remember to call event.preventDefault() so that nobody else (like your persnickety test runner) catches the error and fails your tests.
  • You need to remember to call removeEventListener (or AbortSignal if you’re fancy).

A full-featured utility might look like this:

function catchConnectedError(callback) {
let error;
const listener = event => {
event.preventDefault();
error = event.error;
};
window.addEventListener('error', listener);
try {
callback();
} finally {
window.removeEventListener('error', listener);
}
return error;
}

…which you could use like so:

const error = catchConnectedError(() => {
document.body.appendChild(element);
});
console.log('Caught!', error);

If this comes in handy for you, you might add it to your testing library of choice. For instance, here’s a variant I wrote recently for Jest.

Hope this quick tip was helpful, and keep connectin’ and errorin’!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK