Using the EventTarget interface
Tags: javascript, node, webdev, eventdriven
Published: 2022-08-18 12:17:17

---
title: Using the EventTarget interface
published: true
description: Implementing an isomorphic CustomEvent in Node.js v18 and the browser.
tags: 'javascript, node, webdev, eventdriven'
cover_image: 'https://raw.githubusercontent.com/cloudx-labs/posts/main/posts/navarroaxel/assets/event-target-interface-cover.jpg'
id: 1167436
---

We have learned how to use the [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent) interface in a [previous post](https://dev.to/cloudx/the-customevent-interface-2bfo).

How can we create a progress indicator using the same JavaScript code for both browser and terminal (using Node.js)? For this we can build a `fetch` wrapper with a progress event using the CustomEvent interface, which is compatible with both environments.

📣 The `CustomEvent` interface was added in Node.js [v18.7.0](https://nodejs.org/en/blog/release/v18.7.0/) as an experimental API, and it's exposed on [`global`](https://nodejs.org/dist/latest/docs/api/globals.html#global) using the `--experimental-global-customevent` flag.

## Implementing our event

We need to extend the [`EventTarget`](https://nodejs.org/dist/latest/docs/api/events.html#eventtarget-and-event-api) interface to dispatch events from our custom class so the clients can subscribe to our events.

```javascript
class Http extends EventTarget {
  …

  async get(url) {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(response.statusText);
    }
    const contentLength = this._getContentLength(response);
    const self = this;
    const res = new Response(new ReadableStream({
      async start(controller) {
        const reader = response.body.getReader();
        let loaded = 0;
        try {
          while (true) {
            const {done, value} = await reader.read();
            if (done) {
              break;
            }
            loaded += value.byteLength;
            if (contentLength) {
              self.dispatchEvent(new CustomEvent('progress', {detail: {contentLength, loaded}}));
            }
            controller.enqueue(value);
          }
          controller.close();
        } catch (err) {
          controller.error(err);
        }
      }
    }));
    return res.blob();
  }
}

export default Http;
```

We wrapped the `ReadableStream` instance of the `body` property into a custom implementation to notify the read progress to the listeners of the `progress` event. We should also `read()` all the content of the response until the `done` flag indicates that we've reached the end of the stream.

![Man looking to EventTarget instead of the classic Event emitter pattern](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gnhex9rmqvdcxw2sclsc.png)

## Using our progress event in the terminal

Let's import the `Http` class and add an event listener for the `progress` event. For this example we're going to use a server with download speed up to _30kbps_.

```javascript
const exec = async () => {
  const { default: Http } = await import('./http.mjs');

  const http = new Http();
  const listener = e => console.log(e.detail);
  http.addEventListener('progress', listener);
  await http.get('https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg');
  http.removeEventListener('progress', listener);
}

exec();
```

💡 The listener should be removed to avoid memory leaks in our server. 😉

🧠 We need to use the dynamic `import()` to import ES modules into CommonJS code.

To run this code, we should include the [`--experimental-global-customevent`](https://nodejs.org/dist/latest-v18.x/docs/api/cli.html#--experimental-global-customevent) flag; otherwise the [`CustomEvent`](https://nodejs.org/dist/latest/docs/api/events.html#class-customevent) class will be `undefined`.

```bash
node --experimental-global-customevent index.js
```

## Using our progress event in the browser

Let's create an `index.html` and import our JavaScript module using the following code:

```html
<script type="module">
  import Http from './http.mjs';

  const http = new Http();
  const listener = e => console.log(e.detail);
  http.addEventListener('progress', listener);
  await http.get('https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg');
  http.removeEventListener('progress', listener);
</script>
```

We can run our example locally with the following command:

```bash
npx http-server
```

Now we can navigate to `http://localhost:8080` and check the console output.

## Conclusion

With the `EventTarget` interface we can create reusable code detached from our UI that can be connected to either HTML elements or the terminal to inform progress to our users.

If we don't want to use an experimental API behind the flag in our server we can use the [`EventEmitter`](https://nodejs.org/dist/latest/docs/api/events.html#class-eventemitter) class in Node.js.

![Woman yelling at cat because he said that she can use CustomEvent on Node.js but forgot to mention the experimental flag](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i48twzkgqbad67ll8n6a.png)

You can check the full code example in <https://github.com/navarroaxel/fetch-progress>.

For this post, I have adapted the `fetch-basic` example from <https://github.com/AnthumChris/fetch-progress-indicators> by @AnthumChris.

Open source rocks. 🤘