Using Web Worker multithreading in vue3

Problem Description

Recently, in the project of vue3, I encountered the problem of slow page response due to the huge amount of calculation. It happened that the results of each calculation task did not need to be summarized, so I thought of executing each calculation task in the form of multi-threading. But after a round of google and baidu, no case of vue3 was found. Fortunately, a case on the external network smelled a possibility.


The role of Web Workers

Boss can skip,For Web Worker multi-threading, there may be many small partners who don’t know what it is for, so I will give an example to let everyone know what it is probably used for.

Tip: If you don’t understand the console used, you can learn from this article: Portal

①Wash clothes after taking a shower: Before there was a washing machine (Worker), we usually wash clothes by hand after taking a shower (except for me here, I always step on the clothes and take a bath together) , so we have to spend every day A lot of time is spent on bathing and washing clothes, and the time spent is: 洗澡所用时长+洗衣服所用时长. Equivalent to the following code:

console.time('一共花了多少时长')
console.time('洗澡所用时长')
let length_1 = 300000000;
let sum1 = 0
for (let i = 0; i <= length_1; i++) {
    
    
    sum1 += i
}
console.log('%c循环1执行完:' + sum1, 'color:green')
console.timeEnd('洗澡所用时长')
console.time('洗衣服所用时长')
let length_2 = 200000000;
let sum2 = 0
for (let i = 0; i <= length_2; i++) {
    
    
    sum2 += i
}
console.log('%c循环2执行完:' + sum2, 'color:green')
console.timeEnd('洗衣服所用时长')
console.timeEnd('一共花了多少时长')
// 循环1执行完:45000000067108860
// 洗澡所用时长: 2712.139892578125 ms
// 循环2执行完:20000000067108864
// 洗衣服所用时长: 1751.049072265625 ms
// 一共花了多少时长: 4463.2490234375 ms

GIF output effect: (From the results, it can be seen that loop 1 is executed in about 2.7 seconds, and then loop 2 is executed after about 4.5 seconds, so it can be seen that loop 1 blocks loop 2, so the time spent at this time is : Loop 1 + Loop 2)
insert image description here

② Put the clothes in the washing machine and take a bath: When we have a washing machine (Worker), we can put the clothes in the washing machine and take a bath happily. This is an asynchronous operation, so the total time spent at this time depends on who does the washing last It's over, the time spent is: Math.max(洗澡所用时长,洗衣服所用时长). is equivalent to the following code:

Local working directory:

文件夹
	|-index.html
	|-worker.js

index.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Worker</title>
</head>

<body>

</body>
<script>
    console.time('洗衣机洗完衣服了,所用时长')
    let worker = new Worker("./worker.js"); // 开启副线程
    let length_1 = 300000000;
    let sum1 = 0
    worker.postMessage(length_1); // 发送消息给副线程
    worker.onmessage = e => {
      
       // 监听副线程返回的消息
        sum1 = e.data
        console.log('%c循环1执行完了:' + e.data, 'color:green')
        console.timeEnd('洗衣机洗完衣服了,所用时长')
        worker.terminate() // 关闭线程
    }
    console.time('洗完澡了,所用时长')
    let length_2 = 200000000;
    let sum2 = 0
    for (let i = 0; i <= length_2; i++) {
      
      
        sum2 += i
    }
    console.log('%c循环2执行完了:' + sum2, 'color:green')
    console.timeEnd('洗完澡了,所用时长')
</script>

</html>

worker.js:

self.onmessage = function (e) {
    
     //监听主线程发过来的消息
    let length_1 = e.data
    let sum = 0
    for (let i = 0; i <= length_1; i++) {
    
    
        sum += i
    }
    self.postMessage(sum); // 将信息发送到主线程上
}

GIF output effect: (From the results, it can be seen that the execution of loop 2 is completed in about 1.9 seconds, and then the execution of loop 1 is also completed in about 2.4 seconds. When we put loop 1 on the thread for execution, the subsequent loops are not blocked 2. Because the cycle time of cycle 2 is short, it is printed out first, so the total time spent at this time is: cycle 1) Although it
insert image description here
only saves a few seconds after tossing for so long, when the calculation amount of the project is getting larger and larger, the multi-threaded The advantages will become very obvious.


✘ Using plugins (only for Webpack projects)

In the vue project, if you use Web Worker directly, you will encounter the problem of worker file path and package resolution. When doing vue2 . When used in , the simple-web-worker library it depends on will report Object.defineProperty called on non-objectan error. It should be that vue3 has modularized the Vue global object and it cannot get the object, so I gave it up. After some searching, I found that there is another solution, which is the worker-loader plug-in. Because it is a parser, I initially judged that it can be used in vue3, but I have read many related blog posts, but none of them can be used in vue3. Use it normally in (╯°Д°)╯︵┻━┻, and finally found the available code in this extranet blog post , and successfully ran it in vue3 (T ^ T)

❀ Install worker-loader

Ok, having said so much, let’s get to the point, let’s run npm to install worker-loader first

npm install worker-loader

After the installation is successful, package.jsonyou can see it in the dependencies of the file
insert image description here

❀ Configure webpack

Add configuration parameters in vue.config.js文件defineConfig

  chainWebpack: config => {
    
    
    config.module
      .rule('worker-loader')
      .test(/\.worker\.js$/)
      .use({
    
    
        loader: 'worker-loader',
        options: {
    
    
          inline: true
        }
      })
      .loader('worker-loader')
      .end()
  }

Detailed screenshot:
insert image description here

❀ use

src目录First create a new one workers文件夹, then create a new one inside worker.js, and add the following test code in the js file:

addEventListener('message', e => {
    
    
    const {
    
     data } = e
    console.log(data)
    setTimeout(() => {
    
    
        return postMessage('线程完成')
    }, 1000)
})
export default {
    
    }

Detailed screenshot:
insert image description here
After that, you can create a new vue file and add the following code for testing:

It needs to be used when importing js files worker-loader!@,And the path is several levels from the src directory, not the vue file you execute, which is why I have been unable to run normally.

<template>
  <div>
    <h1>vue3-Worker</h1>
    <button @click="openWorker">开启线程</button>
    <p>F12打开浏览器控制台查看效果</p>
  </div>
</template>

<script setup>
import Worker from 'worker-loader!@/workers/worker'
const openWorker = () => {
      
      
  const worker = new Worker()
  worker.postMessage('开启线程')
  worker.onmessage = e => {
      
      
    console.log(e.data)
    setTimeout(() => {
      
      
      worker.postMessage('线程关闭')
      worker.terminate()
    }, 1000)
  }
}
</script>

For convenience, I directly tested it with the official template HelloWorld.vue:
insert image description here
GIF running effect: (You can see the printing status of the thread in the console console, and you can see all the started threads in the thread Threads of the source code Sources , you can also see it by pressing shift+esc quickly)

insert image description here

Notes on ts project

When you write the code as typeScript (because I returned a string after the thread ran, so the data in the event needs to be declared as a string) , you will find that the imported ts file path will report a wavy line:
insert image description here
at this time we need to shims-vue.d.ts文件declare in Just look at the file path, and declare it directly for all .ts files:
insert image description here

❀Get the project demo

If you have points, please pay the public food. If you don’t have points, just download it from Gitee

CSDN:

vue3-web-worker (js original flavor): portal
vue3-ts-web-worker (ts flavor): portal

Gitee:

vue3-web-worker (js original flavor): portal
vue3-ts-web-worker (ts flavor): portal


✔ Use URL directly (for Vite projects and Webpack projects)

When I was looking at the official document of vite, it turned out that I could directly use URL() to solve the problem of importing files. After trying the vite project and webpack project, both can be used normally. The official address of vite is Portal . For specific use cases, please refer to the following examples.

Local working directory:

src
	|-components
		|-UseWorker.vue
	|-workers
		|-worker.ts

worker.ts:

addEventListener('message', e => {
    
    
    const {
    
     data } = e
    console.log(data)
    setTimeout(() => {
    
    
        return postMessage('线程完成')
    }, 1000)
})

UseWorker.vue:

<template>
    <div>
        <h1>vue3-Worker</h1>
        <button @click="openWorker">开启线程</button>
        <p>F12打开浏览器控制台查看效果</p>
    </div>
</template>
  
<script lang="ts" setup>
const openWorker = () => {
      
      
    const worker = new Worker(new URL('../workers/worker.ts', import.meta.url))
    worker.postMessage('开启线程')
    worker.onmessage = e => {
      
      
        console.log(e.data)
        setTimeout(() => {
      
      
            worker.postMessage('线程关闭')
            worker.terminate()
        }, 1000)
    }
}
</script>

It should be noted that in the vite project we want to worker.tsuse ES Module to export some things

export default {
    
    }

Then we need to add it when we create a new instance type: 'module', otherwise an error will be reported.

const worker = new Worker(new URL('../workers/worker.ts', import.meta.url), {
    
    
    type: 'module',
})

In addition, for the vite project, we can also vite.config.tsconfigure the Worker option, see the official document for details: Portal

▲Asynchronous task processing

Some friends may encounter a situation where there are multiple calculation tasks, but the final result needs to be summarized. At this time, we need to use it to Promise.alldeal with it. For details, refer to the following example:

Promise.allIt will wait for all asynchronous events to be executed before calling back the result to then. Friends who don’t understand can go to tutoring by themselves: Portal

Local working directory:

文件夹
	|-cumsumWorker.js
	|-index.html

cumsumWorker.js: (simple accumulation task)

self.onmessage = function (e) {
    
    
    let length = e.data
    let sum = 0
    for (let i = 0; i <= length; i++) {
    
    
        sum += i
    }
    self.postMessage(sum);
}

index.html: We open a sub-thread for each calculation task to calculate the respective accumulation tasks, and then wait for all the sub-threads to complete the calculation in the main thread before processing the results

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Worker</title>
</head>

<body>

</body>
<script>
    // 计算任务1
    let calculate1 = new Promise((resolve, reject) => {
      
      
        let worker = new Worker("./cumsumWorker.js");
        let length = 1000000000;
        worker.postMessage(length);
        worker.onmessage = e => {
      
      
            worker.terminate()
            resolve(e.data)
        }
    })
    // 计算任务2
    let calculate2 = new Promise((resolve, reject) => {
      
      
        let worker = new Worker("./cumsumWorker.js");
        let length = 2000000000;
        worker.postMessage(length);
        worker.onmessage = e => {
      
      
            worker.terminate()
            resolve(e.data)
        }
    })
    // 任务汇总
    Promise.all([calculate1, calculate2]).then((res) => {
      
      
        console.log(res)
    })
</script>

</html>

GIF output effect:

insert image description here


over~~

Guess you like

Origin blog.csdn.net/weixin_42063951/article/details/125300644