Using Web Workers In Vue Applications
Tags: vue, javascript, softwareengineering
Published: 2023-07-15 16:13:04

> This post assumes you have basic knowledge of Javascript and Vue.

## Single-Threaded JavaScript

As a single-threaded programming language, Javascript can only perform a single set of instructions at a time. This means that every other process has to wait for one instruction to complete before the next process is executed.

This would pose a speed problem in our web application if we had to carry out heavy processes and also want the user to be still able to interact with our application. Say we needed to calculate the return on investment of multiple investment packages and still ensure these processes are non-blocking and do not run on the main thread, so we can perform other tasks on the main thread, like making a network request…Enters **web workers**.

> Note: **blocking** refers to serial processing — Doing one thing at a time. **Non-blocking** on the other hand code (asynchronous) can run in parallel (or multithreaded)

Web workers allow us to run processes in the background thread and notify the main thread when those processes are complete. This adds a great performance boost as we do not need to chunk up everything on the main thread.

## Web workers

> Web Workers are a simple means for web content to run scripts in background threads. The worker thread can perform tasks without interfering with the user interface. Once created, a worker can send messages to the JavaScript code that created it by posting messages to an event handler specified by that code (and vice versa). — [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers)

## Web workers in Javascript

![Webworkers in Javascript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xh83evg77n5e6ic8ljhy.png)

The main thread creates the worker using the “Worker” constructor. This constructor takes the path to the worker file in a single argument. The worker file contains the code that will run in the worker thread; workers run in another global context that is different from the current `window`.

Data is passed between the worker and the main thread via messages — the main thread and worker thread send messages using the `postMessage()` method, and respond to messages sent using the `onmessage` handler.

Here’s a simple example of implementing web workers in Javascript.

{% embed https://github.com/MartinsOnuoha/js-webworker-example %}

---

## Using Web workers in Vue

To use web workers in a Vue application, we can either use a web worker wrapper for vue like the [vue-worker](https://www.npmjs.com/package/vue-worker) package or implement it low-level (build from the ground up). I’ll keep it simple and build this example without the vue-worker package so we understand what happens under the hood.

Let’s set up our vue application.

To keep it simple, I’ll use the [Vue CDN](https://vuejs.org/v2/guide/installation.html#CDN) in a plain HTML page instead of using the [vue-cli](https://cli.vuejs.org/) to generate a project. Let’s set up our application folder. Our folder structure would look something like this:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7rixjw2d7g0bzrqdew9d.png)

---

## Application

We’ll implement the same [Javascript example](https://github.com/MartinsOnuoha/js-webworker-example) in Vue (a countdown timer). Since the countdown timer is a long-running process, we’ll delegate it to our web worker and trigger a method on our main thread to fetch a random dog image from [this API](https://dog.ceo/api/breeds/image/random) when our counter value is divisible by 10. The result looks something [like this](https://silent-wish.surge.sh/):


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aos05cio4yrw85btejyt.png)

{% embed https://silent-wish.surge.sh/ %}

---

## Markup

In our index.html file, we’ll structure our markup, including the vue-next CDN link, our main script file, and the mount point for vue.

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Vue-Webworker</title>
  <link rel="stylesheet" href="./styles/app.css">
</head>
<body>
  <div id="app">
    <div class="dog-image">
      <img :src="dogImage" v-if="dogImage" />
    </div>

    <p v-if="counter">{{ counter }}</p>
  </div>

  <script src="https://unpkg.com/vue@next"></script>
  <script src="./script/app.js"></script>
</body>
</html>
```


We’ve imported our CSS file at the top and our scripts at the bottom. We’re also displaying a counter property from our data object when it’s available. We also render the “**dogImage**” value with the image tag when available.

Next, we’ll set up our vue app in our app.js entry file.

Because this example application depends heavily on the web workers, we need first to check that the browser supports Workers then we instantiate a new Worker class.

```javascript
if (window.Worker) {
  const worker = new Worker('/script/worker.js')
  worker.postMessage('')

  ...
}
```

Next, we set up our App object.

```javascript
const App = {
  ...
}
```

We’ll have two data properties:

```javascript
const App = {
  data() {
    return {
      dogImage: null,
      counter: 0,
    }
  }
}
```

In our methods object, we’ll create a single method to fetch random dog images from the dog API.

```javascript
  ...

  methods: {
    getDogImage () {
      fetch('https://dog.ceo/api/breeds/image/random')
        .then((response) => response.json())
        .then((data) => {
          this.dogImage = data.message
        })
      }
  }

  ...
```


Finally, in the mounted hook, we’ll call the **getDogImage** method the first time, then set up an **onmessage** listener on our worker object to listen to updates from the worker thread. We check if the counter value sent by the worker is divisible by 10; if it is, we call the **getDogImage** method again.

```javascript
  ...
  mounted () {
    this.getDogImage()
      worker.onmessage = (e) => {
        this.counter = e.data
        if (this.counter % 10 === 0) {
          this.getDogImage()
        }
      }
    },
  ...
```

We then mount the App object on the #app element.

```javascript
  Vue.createApp(App).mount('#app')
```

The entire app.js file should look something like this:

```javascript
if (window.Worker) {
  const worker = new Worker('/script/worker.js')
  worker.postMessage('')


  const App = {
    data () {
      return {
        dogImage: null,
        counter: 0
      }
    },
    mounted () {
      this.getDogImage()

      worker.onmessage = (e) => {
        this.counter = e.data
        if (this.counter % 10 === 0) {
          this.getDogImage()
        }
      }
    },
    methods: {
      getDogImage () {
        fetch('https://dog.ceo/api/breeds/image/random')
          .then((response) => response.json())
          .then((data) => {
            this.dogImage = data.message
          })
      },
      showUpdate(e) {
        console.log(e)
      }
    }
  }

  Vue.createApp(App).mount('#app')
}
```


In our worker.js file, we’ll set up the counter logic to count up to 1000 before resetting. We use the **postMessage** method to send the new counter value to the main thread, which will be received by the **onmessage** listener.

```javascript
onmessage = (e) => {
  let counter = 0
  setInterval(() => {
    counter++

    if (counter === 1000) {
      counter = 0
    }
    postMessage(counter)
  }, 1000)
}

```

---

For a little bit of aesthetic, we’ll add some styling. Add this within the app.css file.

```css
body {
  background-color: #8EC5FC;
  background-image: linear-gradient(62deg, #8EC5FC 0%, #E0C3FC 100%);
}
#app {
  text-align: center;
  display: flex;
  flex-direction: column;
}
p {
  font-size: 5rem;
  color: #FFF;
}
.dog-image {
  width: 100%;
  height: 300px;
  max-height: 300px;
  margin: 0 auto;
}
img {
  object-fit: contain;
  border-radius: 10px;
}
```


You can start the application with LiveServer on VScode.


![Open Live Server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n66w2ja15i4kbbmdpc5c.png)

You should find the application running on port `5500`.

Check out the [source code](https://github.com/MartinsOnuoha/vue-webworker-example) of the example app used.

{% embed https://github.com/MartinsOnuoha/vue-webworker-example %}

---

Cheers ☕️