使webworker中支持使用import导入模块——threads.js
1、threads.js基本使用
1.1 使用vue-cli创建一个项目(我这里vue --version的版本是3.11.0):
vue create hello-world
1.2 添加tool.js文件:
export function getSuffix() { return new Date().toDateString() }
1.3 添加webworker.js文件,import引入tool.js文件,以验证webworker中支持import:
import sha256 from "js-sha256" import { expose } from "threads/worker" import { getSuffix } from './tool' expose({ hashPassword(password, salt) { let suffix = getSuffix(); return sha256(password + salt) + `【当前时间:${suffix}】` } })
1.4 添加master.js文件,
import { spawn, Thread, Worker } from "threads" export async function getHashPassword() { const auth = await spawn(new Worker("./webworker")) const hashed = await auth.hashPassword("Super secret password", "1234") console.log("Hashed password:", hashed) await Thread.terminate(auth) return hashed; }
注意,Worker
从 threads.js 导入。
其中:
spawn()
创建一个新的工人expose()
声明您希望您的工作人员公开哪些功能Thread.terminate()
一旦你不再需要它就杀死工人
1.5 HelloWorld.vue文件中调用getHashPassword:
<script> import { getHashPassword } from "../util/master"; export default { name: "HelloWorld", props: { msg: String, }, data() { return { myMsg: [], }; }, mounted() { console.log(Date.now().toString()); getHashPassword(Date.now().toString()).then((res) => { this.myMsg = res; }); }, }; </script>
项目目录结构:
1.6 安装依赖:
npm install threads tiny-worker
1.7 使用 webpack 构建
Webpack 配置与threads-plugin
一起使用。
它将透明地检测所有new Worker("./unbundled-path")
表达式,捆绑工作代码并将new Worker(...)
路径替换为工作包路径,因此您无需显式使用worker-loader
或定义额外的入口点。
安装threads-plugin:
npm install -D threads-plugin
然后将其添加到您的webpack.config.js
:
const ThreadsPlugin = require('threads-plugin') module.exports = { // ... plugins: [ + new ThreadsPlugin() ] // ... }
1.8 效果:
npm run serve后:
图中当前时间能正常输出证明tool.js被正常import了。
官方文档:https://threads.js.org/getting-started
2、threads.js线程池
官方文档:https://threads.js.org/usage-pool
线程池
允许我们创建一组工作人员和队列工作人员调用。排队的任务从队列中拉出并在前面的任务完成时执行。
如果有大量工作要卸载给工作人员并且不想让他们一下子淹没在一堆工作中,但要以受控的方式以有限的并发方式运行这些任务,就可以选择使用线程池。
基于1改动代码:
2.1 webworker.js:
// workers/auth.js - will be run in worker thread import sha256 from "js-sha256" import { expose } from "threads/worker" import { getSuffix } from './tool' expose({ hashPassword(password, salt) { let suffix = getSuffix(); for (let index = 0; index < 100000; index++) { console.log(index) } return sha256(password + salt) + `【当前时间:${suffix}】` } })
2.2 master.js:
import { spawn, Pool, Worker } from "threads" export function getHashPassword() { return new Promise(async (resolve) => { const pool = Pool(() => spawn(new Worker("./webworker")), 8); let resultList = []; let myTasks = []; for (let input = 0; input < 10; input++) { const task = pool.queue(worker => worker.hashPassword(input.toString(), "1234")) task.then((res) => { resultList.push(res); }) myTasks.push(task) } await Promise.all(myTasks); resolve(resultList); await pool.completed() await pool.terminate() }) }
传递给工厂的第一个参数Pool()
必须是一个生成选择的工作线程的函数。线程池将使用这个函数来创建它的Worker。
第二个参数是可选的,可以是number也可以是option选项对象(请参阅PoolOptions
):
options.concurrency
:每个Worker同时运行的任务数,默认为一个options.maxQueuedJobs
: 抛出之前要排队的最大任务数.queue()
,默认为无限制options.name
:给池一个自定义名称以在调试日志中使用,这样你就可以在调试时区分多个池options.size
: 要产生的工人数,默认为 CPU 核心数
使用pool.queue()进行排队,使用
task.then的形式而不是await
,因为在任务运行并完成之前,此行之后的代码将不会运行。
每当池Worker完成一项工作时,下一个池Worker就会出列(即您传递给的函数pool.queue()
)。它以 worker 作为第一个参数调用。工作函数应该返回一个承诺——当这个承诺被解决时,工作被认为已经完成,下一个工作被出队并分派给Worker。
2.3 效果:
2.4 等待任务完成
该池带有两种允许await
完成所有任务的方法。
第一个是pool.completed()
。它返回一个承诺,一旦所有任务都已执行并且没有更多任务要运行,该承诺就会解决。如果任务失败,承诺将被拒绝。
第二个是pool.settled()
。它还会返回一个承诺,该承诺会在所有任务都已执行时解决,但如果任务失败,它也会解决而不是拒绝。返回的承诺解析为错误数组。
如前所述,池任务提供了类似 Promise 的.then()
方法。您可以使用它仅等待池中排队任务的子集完成。
// (Created a pool and queued other pool tasks before…) const myTasks: QueuedTask[] = [] for (let input = 0; input < 5; input++) { const task = pool.queue(worker => worker.work(input)) myTasks.push(task) } await Promise.all(myTasks) console.log("All worker.work() tasks have completed. Other pool tasks might still be running.")
2.5 池终止
// Terminate gracefully pool.terminate() // Force-terminate pool workers pool.terminate(true)
默认情况下,池将等到所有计划任务完成后再终止Worker。通过true
立即强制终止池。
2.6 取消排队的任务
可以按以下方式取消排队的任务。但是,如果池已经开始执行任务,则不能再取消它。
const task = pool.queue(multiplierWorker => multiplierWorker(2, 3)) task.cancel()
3、【No instantiations of threads.js workers found】问题的解决
在上述的demo项目worker都是可以正常使用的,但是在我自己的项目始终提示:
No instantiations of threads.js workers found. Please check that: 1. You have configured Babel / TypeScript to not transpile ES modules 2. You import `Worker` from `threads` where you use it
将demo项目总的文件放到自己项目,并把自己项目的webpack的入口改为demo项目的文件,HtmlWebpackPlugin的模板index.html改为demo项目的index.html后,并把webpack中rules规则全注释后,在自己项目也可以运行demo项目的功能了,确定了不是webpack或者threads-plugin的版本问题导致的。
将webpack中rules规则一部分一部分放开后,发现是babel配置导致的:
{ test: /\.js$/, exclude: /node_modules/, use: { loader: "babel-loader" } },
而看threads-plugin文档对babel的配置需要作以下调整:
"presets": [ ["env", { "modules": false }] ]
使用 Babel 或 TypeScript 转译源代码时,请确保 ES 模块是由 webpack 转译的,而不是由 Babel 或 TypeScript 转译的。否则线程插件将无法识别导入。
而使用上述配置后,会提醒:Cannot find module 'babel-preset-env' - Did you mean "@babel/env"?,所以又改成:
"presets": [ ["@babel/preset-env", {"modules": false} ] ]
仍然提示:
No instantiations of threads.js workers found. Please check that: 1. You have configured Babel / TypeScript to not transpile ES modules 2. You import `Worker` from `threads` where you use it
最终对项目中的babel.config.js使用二分法注释排查后,发现是@babel/plugin-transform-modules-commonjs导致的,而看官网,这个项目的作用是:这个插件将 ECMAScript 模块转换为CommonJS,也就是跟上述配置起类似作用,把这个删除后就可以在自己的项目里跑threads.js了~
不过后来发现打包又有问题了,后续继续调查原因。