前言

Web Workers 是 2009 年就已经提案的老技术,但是在很多项目中的应用相对较少,常见一些文章讨论如何写 demo ,但很少有工程化和项目级别的实践,本文会结合 Web Workers 在京东羚珑的程序化设计项目中的实践,分享一下在当下的 2023 年,关于 worker 融入项目的一些思考和具体的实现方式,涉及到的 demo 已经放在 github 上附在文末,可供参考。

先简单介绍下 Web Workers,它是一种可以运行在 Web 应用程序后台线程,独立于主线程之外的技术。众所周知,JavaScript 语言是单线程模型的,而通过使用 Web Workers,我们可以创造多线程环境,从而可以发挥现代计算机的多核 CPU 能力,在应对规模越来越大的 Web 程序时也有较多收益。

Web Workers 宏观语义上包含了三种不同的 Worker: DedicatedWorker(专有worker)SharedWorker(共享Worker)ServiceWorker,本文讨论的是第一种,其他两种大家可以自行研究一下。

引入 Web Worker

当引入新技术时,通常我们会考虑的问题有:1、兼容性如何? 2、使用场景在哪?

问题 1,Web Workers 是 2009 年的提案,2012 年各大浏览器已经基本支持,11 年过去了,现在使用已经完全没有问题啦

问题 2,主要考虑了以下 3 点:

1.Worker API 的局限性:同源限制、无 DOM 对象、异步通信,因此适合不涉及 DOM 操作的任务
2.Worker 的使用成本:创建时间 + 数据传输时间;考虑到可以预创建,可以忽略创建时间,只考虑数据传输成本,这里可参考 19 年的一个测试 Is postMessage slow ,简要结论是比较乐观的,大部分设备和数据情况下速度不是瓶颈
3.任务特点:需要是可并行的多任务,为了充分利用多核能力,可并行的任务数越接近 CPU 数量,收益会越高。多线程场景的收益计算,可以参考 Amdahl 公式,其中 F 是初始化所需比例,N 是可并行数:

综上结论是,可并行的计算密集型任务适合用 Worker 来做。

Worker 实践

介绍完 worker ,一个问题出现了:为什么一个兼容性良好,能够发挥并发能力的技术(听起来很有诱惑力),到现在还没有大规模使用呢?

我理解有 2 个原因:一是暂无匹配度完美的使用场景,因此引入被搁置了;二是 worker api 设计得太难用,参考很多 demo 看,限制多配置还麻烦,让人望而却步。本文会主要着力于第二点,希望给大家的 worker 实践提供一些成熟的工程化思路。

至于第一点理由,在如此卷的前端领域,当你手中已经有了一把好用的锤子,还找不到那颗需要砸的钉子吗?

Worker 到底有多难用

下面是一个原始 worker 的调用示例,上面是主线程文件,下面是 worker 文件:

// index.js
const worker = new Worker("./worker.js");
worker.onmessage = function (messageEvent) {console.log(messageEvent);
};
// worker.js
importScripts("constant.js");
function a() {console.log("test");
}

其中问题有:

1.postMessage 传递消息的方式不适合现代编程模式,当出现多个事件时就涉及分拆解析和解决耦合问题,因此需要改造
2.新建 worker 需要单独文件,因此项目内需要处理打包拆分逻辑,独立出 worker 文件
3.worker 内可支持定义函数,可通过importScript 方式引入依赖文件,但是都独立于主线程文件,依赖和函数的复用都需要改造
4.多线程环境必然涉及同步运行多个 worker,多 worker 的启动、复用和管理都需要自行处理

看完这么多问题,有没有感觉头很大,一个设计这样原始的 api,如何舒服的使用呢?

类库调研

首先可以想到的就是借助成熟类库的力量,下面表格是较为常见的几款 worker 类库,其中我们可能会关注的关键能力有:

1.通信是否有包装成更好用的方式,比如 promise 化或者 rpc
2.是否可以动态创建函数——可以增加 worker 灵活性
3.是否包含多 worker 的管理能力,也就是线程池
4.考虑 node 的使用场景,是否可以跨端运行

比较之下,workerpool 胜出,它也是个年纪很大的库了,最早的代码提交在 6 年前,不过实践下来没有大问题,下文都会在使用它的基础上继续讨论。

有类库加持的 worker 现状

通过使用 workerpool,我们可以在主线程文件内新建 worker;它自动处理多 worker 的管理;可以执行 worker 内定义好的函数 a;可以动态创建一个函数并传入参数,让 worker 来执行。

// index.js
import workerpool from "workerpool";
const pool = workerpool.pool("./worker.js");
// 执行一个 worker 内定义好的函数
pool.exec("a", [1, 2]).then((res) => {console.log(res);
});
// 执行一个自定义函数
pool.exec((x, y) => {return x + y;}, // 自定义函数体[1, 2] // 自定义函数参数).then((res) => {console.log(res);});
// worker.js
importScripts("constant.js");
function a() {console.log("test");
}

但是这样还不够,为了可以舒适的写代码,我们需要进一步改造

向着舒适无感的 worker 编写前进

我们期望的目标是:

1.足够灵活:可以随意编写函数,今天我想计算1+1,明天我想计算1+2,这些都可以动态编写,最好它可以直接写在主线程我自己的文件里,不需要我跑到 worker 文件里去改写
2.足够强大:我可以使用公共依赖,比如 lodash 或者是项目里已经定义好的某些公共函数

考虑到 workerpool 具备了动态创建函数的能力,第一点已经可以实现;而第二点关于依赖的管理,则需要自行搭建,接下来介绍搭建步骤

1.抽取依赖,管理编译和更新:

新增一个依赖管理文件worker-depts.js,可按照路径作为 key 名构建一个聚合依赖对象,然后在 worker 文件内引入这份依赖

// worker-depts.js
import * as _ from "lodash-es";
import * as math from "../math";const workerDepts = {_,"util/math": math,
};export default workerDepts;
// worker.js
import workerDepts from "../util/worker/worker-depts";

2.定义公共调用函数,引入所打包的依赖并串联流程:

worker 内定义一个公共调用函数,注入 worker-depts 依赖,并注册在 workerpool 的方法内

// worker.js
import workerDepts from "../util/worker/worker-depts";function runWithDepts(fn: any, ...args: any) {var f = new Function("return (" + fn + ").apply(null, arguments);");return f.apply(f, [workerDepts].concat(args));
}workerpool.worker({runWithDepts,
});

主线程文件内定义相应的调用方法,入参是自定义函数体和该函数的参数列表

// index.js
import workerpool from "workerpool";
export async function workerDraw(fn, ...args) {const pool = workerpool.pool("./worker.js");return pool.exec("runWithDepts", [String(fn)].concat(args));
}

完成以上步骤,就可以在项目任意需要调用 worker 的位置,像下面这样,自定义函数内容,引用所需依赖(已注入在函数第一个参数),进行使用了。

这里我们引用了一个项目内的公共函数 fibonacci,也引用了一个 lodashmap 方法,都可以在depts 对象上取到

// 项目内需使用worker时
const res = await workerDraw((depts, m, n) => {const { map } = depts["_"];const { fibonacci } = depts["util/math"];return map([m, n], (num) => fibonacci(num));},input1,input2
);

3.优化语法支持

没有语法支持的依赖管理是很难用的,通过对 workerDraw 进行 ts 语法包装,可以实现在使用时的依赖提示:

import workerpool from "workerpool";
import type TDepts from "./worker-depts";export async function workerDraw<T extends any[], R>(fn: (depts: typeof TDepts, ...args: T) => Promise<R> | R,...args: T
) {const pool = workerpool.pool("./worker.js");return pool.exec("runWithDepts", [String(fn)].concat(args));
}

然后就可以在使用时获取依赖提示:

4.其他问题

新增了 worker 以后,出现了 windowworker 两种运行环境,如果你恰好和我一样需要兼容 node 端运行,那么运行环境就是三种,原本我们通常判断 window 环境使用的也许是 typeof window === 'object'这样,现在不够用了,这里可以改为 globalThis 对象,它是三套环境内都存在的一个对象,通过判断globalThis.constructor.name的值,值分别是'Window' / 'DedicatedWorker'/ 'Object',从而实现环境的区分

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

2023 年的 Web Worker 项目实践相关推荐

  1. 【ASP.NET Web】项目实践—网上宠物店11:制作“搜索”页面

    用户在搜索框里输入关键词,点击搜索按钮时,给出相关的产品. 1.制作搜索页 SearchProducts.aspx ,完成页面布局和基本设计. 分析商品展示页面的界面:内容实现,要写在占位符控件中.搜 ...

  2. 【ASP.NET Web】项目实践—网上宠物店5:创建用户登录/注销页面、完善母版页

    基于数据库的 Web 应用程序都需要考虑网站的安全性,网站离不开权限管理,包括用户身份识别和用户授权两部分.识别用户身份通过登录来实现.用户授权是根据不同的用户身份判断是否允许其访问某个网页或执行某些 ...

  3. 浅析Web Worker及实践

    本文讲述内容如下所示: Web Worker概述 API Web Worker在实际项目中应用 总结 一.Web Worker概述 1.Web Worker产生背景 众所周知JavaScript是单线 ...

  4. 【ASP.NET Web】项目实践—网上宠物店2:创建ASP.NET Web 网站项目、连接数据库

    1.创建网站 打开VS软件(项目当中使用的软件版本是 VS2012 ) ,依次点击 [文件]→[新建]→[项目],在弹出的"新建项目" 窗口中,新建一个 ASSP.NET 空 We ...

  5. 【ASP.NET Web】项目实践—网上宠物店4:创建母版页

    在使用 ASP.NET 开发网上宠物店项目的时候,网站除主页外,其余页面均有相似的布局.为了使网站具有统一的风格,并减少页面设计的工作量,网站使用  ASP.NET 母版页技术来实现. 先根据给定的效 ...

  6. 【ASP.NET Web】项目实践—网上宠物店1:数据库设计

    1.项目演示 1.1  界面效果 1.2  功能介绍 2.数据库设计 网站数据表 数据库表 说明 Account 用户表,保存用户账号信息及地址等用户资料 Category

  7. 【ASP.NET Web】项目实践—网上宠物店8:制作“商品展示”页面

    网站通过 Products.aspx 页面展示所销售的宠物. 分析商品展示页面的界面:内容实现,要写在占位符控件中. 分析产品展示页面的功能: Products.aspx 页面使用一个 DataLis ...

  8. 【ASP.NET Web】项目实践—网上宠物店7:制作“个人资料”页面

    用户注册只手机了用户登录所需要的用户名和密码,其他与具体业务相关的用户信息还需要自行维护管理.宠物商店需要保存用户的联系信息,包括地址.电话和电子邮箱等,以便用户在订购宠物时获得送货与联系信息,这些信 ...

  9. 【ASP.NET Web】项目实践—网上宠物店6:制作“用户注册”页面

    PetShop 网站的用户分为普通访客和注册用户两类.普通访客可以浏览主页及宠物产品页面,注册用户可以订购商品.普通访客通过注册即可成为注册用户. 分析注册页面的界面:内容实现,要写在占位符控件中. ...

最新文章

  1. tensorflow-gpu
  2. hbuilder打包的app如何设置自动清理缓存_手机空间不足,这些“隐形”文件如何找到删除?...
  3. Python Django 配置URL的方式(url传参方式)
  4. Linux设备驱动之I/O端口与I/O内存
  5. AE开发右键缩放至图层
  6. TYUT-A专题题解(二)
  7. Java基础:Collection集合、Iterator迭代器以及泛型
  8. macOS Monterey 12.0.1(21A559) 正式版三分区原版黑苹果镜像
  9. SSH 只能用于远程 Linux 主机?那说明你见识太小了!
  10. 有向图和无向图转化为邻接表后链表中结点个数
  11. 迪杰斯特拉和弗洛伊德算法
  12. 台式计算机设置热点,台式机怎么设置无线热点
  13. ISP中的Lens shading整理不易
  14. 【报告分享】2021年618全面复盘报告-魔镜市场情报(附下载)
  15. Queen Collisions
  16. 技术干货| MindSpore新一代自主研发分子模拟库:Mind-Sponge
  17. emby,jellyfin,kodi系列
  18. 人机对话这件事为什么难?| 清华x-lab人工智能研习社
  19. 垂杨柳中学2021年高考成绩查询时间,2021年中考成绩
  20. Java 使用IE浏览器下载文件,文件名乱码问题

热门文章

  1. 1、Visual Studio 2017安装
  2. 计算机对英语写作的帮助,2018年6月英语六级写作范文:计算机对写作能力的影响...
  3. 20210703-vue在Mac上的安装和初始化及遇到的问题
  4. 应用层协议 —— HTTP(二)
  5. Ping 命令详解(含真实操作截图)
  6. 船舶爬虫出现过的问题
  7. Maven中央仓库地址大全,Maven中央仓库配置示例
  8. Apache ServiceComb社区常见问题解答问答精选(第一期)
  9. 7-8 病毒感染检测 (10分)
  10. python基础之语言特点