DEV Community

Cover image for Using the EventTarget interface
Axel Navarro for Cloud(x);

Posted on

Using the EventTarget interface

We have learned how to use the CustomEvent interface in a previous post.

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 as an experimental API, and it's exposed on global using the --experimental-global-customevent flag.

Implementing our event

We need to extend the EventTarget interface to dispatch events from our custom class so the clients can subscribe to our events.

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;
Enter fullscreen mode Exit fullscreen mode

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

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.

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();
Enter fullscreen mode Exit fullscreen mode

💡 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 flag; otherwise the CustomEvent class will be undefined.

node --experimental-global-customevent index.js
Enter fullscreen mode Exit fullscreen mode

Using our progress event in the browser

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

<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>
Enter fullscreen mode Exit fullscreen mode

We can run our example locally with the following command:

npx http-server
Enter fullscreen mode Exit fullscreen mode

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 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

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. 🤘

Top comments (0)