// 先在 sort-worker.js 中注册事件handler
let receiver = new Receiver()
receiver.registerAction('sort', (data) => {
let result = sort(data) // 这是一个耗时长的排序
return result
})
// 以下代码运行在主线程
import Worker from './sort-worker.js'
// 这里的 Worker 使用 worker-loader 加载,下文会提到
let worker = new Worker()
let sender = new Sender(worker)
sender.postAction('sort', data)
.then((result) => {
// 拿到计算结果
console.log(result)
})
问题
main线程 与 worker线程 的数据传递使用的是拷贝传递。如果传递的 data 很大,序列化(JSON.stringify)和反序列化(JSON.parse)也会非常耗时,阻塞主线程。
代码2.0
第一原则:尽量减少数据在 main 与 worker 之间的传递 (避免不必要的序列化和反序列化开销)
第二原则:如果一定要传递,将大对象(Object,Array)做拆分,分批序列化、传递过来 (减少对主线程的占用)
// 执行顺序
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
背景
一个搜索页面,发送请求拿到数据后,需要对数据做一些计算和处理,然后展示出来。在数据量大的情况下,这个数据处理过程会很耗时,造成浏览器卡顿 or 卡死 or 崩溃。 原因是因为
js
代码和UI
渲染运行在同一个线程,js
执行时间过长影响到了正常的渲染。方案1
减少
js
代码的执行时间,给浏览器渲染的机会。将大量计算拆分为微任务,控制每个微任务的执行时长。setTimeout
、requestAnimationFrame
...方案2
在另外一个线程执行运算,不干扰主线程。
Web Worker
...更多
(没想到更多,欢迎补充)
Web Worker介绍
基于事件通信
需要注意的地方
dom
self
)和执行环境(与主线程互不干扰),没有window
对象CORS
无效)worker
线程的数据传递是拷贝传输(可以直接传递给worker
,但是有条件限制transferable objects)代码1.0
原始的
Worker API
是基于事件的,过于底层,不方便集成到我们的业务代码中来。需要做一层封装总的来说,希望实现以下两个目标:
trigger
) and 事件处理(handler
) 代码即可Promise
,这样就能将耗时长的计算(函数)视为一个异步操作, 同时配合await
,现有代码无需做太大改动就能接入整个过程如下
将耗时长的函数放到
worker
中,形成如下代码。问题
main
线程 与worker
线程 的数据传递使用的是拷贝传递。如果传递的data
很大,序列化(JSON.stringify
)和反序列化(JSON.parse
)也会非常耗时,阻塞主线程。代码2.0
第一原则:尽量减少数据在
main
与worker
之间的传递 (避免不必要的序列化和反序列化开销) 第二原则:如果一定要传递,将大对象(Object,Array)做拆分,分批序列化、传递过来 (减少对主线程的占用)目前计算场景
优化
因此在
Sender
和Receiver
的基础上稍做扩展,形成Manager
和Register
不同在于:
与组件结合
不需要感知太多的细节,同时将
worker
生命周期与组件绑定。异常处理
worker.onerror
try...catch
worker
中的handler
,在执行时会包裹在try...catch
块中,如果执行出错,会将异常抛回给Main
线程。理论上来说,所有的错误都能通过这种类似同步的方式被捕获。 但是还有可能,worker
在加载的时候就出错了,这个时候只能通过worker.onerror
事件来拿到错误信息。其他
同源策略
方案
Blob URL 格式为
blob:[origin]/[uuid]
blob:http://localhost:7777/ceb1fcc2-cadd-4d7e-b754-e79879589e8d
在
worker
中碰到url
时要注意,不能直接写/xxx/yy
,需要对手动的做一层处理,添加origin
与webpack的结合
webpack
的loader
本质上只做一件事情, 即worker-loader
使用
worker-loader
来加载worker
代码,其原理如下。为了保证
worker
中的代码被babel
转译,需要让babel-loader
在worker-loader
之前执行loader的执行顺序
loader
自身的执行顺序是从后往前的。所以将
worker-loader
放到最前面polyfill的处理
worker
中的环境是独立于window
的,因此在window
中引入的polyfill
,还需要在worker
中再引入一次。importScripts
用于在worker
中加载其他脚本,并且它是 同步加载 的,在所有代码执行前引入即可。问题还没有彻底解决,有以下代码
async
函数的转译依赖于generator
的polyfill
,babel@6 转译后生成如下代码。babel@7 没有这个问题,如下所示
如果项目使用的是
babel@6
,则不能在worker
中直接使用async
/generator
window 对象
将部分代码搬到
worker
中要注意worker
环境的限制,比如没有window
,要将代码做一些改动才能正常运行。方案
浏览器内存限制
不同浏览器(chrome/firefox...),浏览器的不同版本(version)、不同平台(64bi/32bit,windows/macOS) 都有可能存在差异。
做了个测试,结果仅供参考。
当数组长度增加到
240W
时,chrome
有如下提示(也可能因为版本差异没有该提示)最后一点点感想
感觉大数据量场景下的复杂计算,前端能做的事情有限(浏览器限制,只有一个主线程,内存限制,不能读写文件,多线程不能共享内存。。)
碰到瓶颈,只能尽量的去优化数据结构与算法,如果 数据量真的到了一定级别 还是在后端做比较好。
毕竟后端能做的事情太多。。。
---------------------------- 🐶 END 🐶 -----------------------------