我们的 AI 助手有个功能:让它分析一份导出的数据,返回一大坨结构化 JSON,前端拿到后渲染成图表和表格。问题来了——有时候这坨 JSON 好几兆,主线程 JSON.parse 一下,界面直接卡死大半秒,流式输出的光标都顿住了。这事儿用 Web Worker 解决得挺漂亮,记一记。
为啥会卡
JSON.parse 是同步的,而且没法中断。几 MB 的 JSON 解析就是几百毫秒的纯计算,这段时间主线程啥也干不了,动画卡顿、输入无响应。AI 场景里这尤其难受,因为对话框还在流式更新,一卡就破功。
把解析甩进 Worker
新建一个 worker,把原始字符串扔过去解析完再传回来:
// main.js
const worker = new Worker(new URL('./parse.worker.js', import.meta.url))
worker.postMessage(rawJsonString)
worker.onmessage = e => render(e.data)
// parse.worker.js
self.onmessage = e => {
const parsed = JSON.parse(e.data)
self.postMessage(parsed)
}
主线程只管发数据、收结果,中间那几百毫秒的解析完全不占主线程,界面丝滑。Vite / webpack 现在都原生支持 new Worker(new URL(...)) 这种写法,不用额外配 loader。
坑:postMessage 也有开销
我一开始以为甩进 worker 就万事大吉,结果发现解析后的大对象通过 postMessage 传回主线程,会走结构化克隆,大对象克隆本身也耗时,等于卡的地方从 parse 挪到了传输。
部分场景可以用 Transferable(比如 ArrayBuffer)零拷贝传输,但普通对象享受不到。我的折中:在 worker 里就把数据加工成最终要用的精简结构,只回传必要字段,减小传输量。
什么时候别用 Worker
小 JSON(几十 KB)根本不卡,搞 worker 反而增加通信开销和复杂度,得不偿失。我设了个阈值,数据小于某个大小直接主线程 parse,超了才走 worker。别无脑上 worker。
一个没做的
worker 我目前是用一次创建一次,频繁解析的话应该做个 worker 池复用,避免反复创建销毁的开销。量不大暂时没做,先记着。
那坨 JSON 是 AI 生成的结构化数据,模型我直接调讯飞 MaaS,现成接口返回,前端专心把解析性能压下去。你们处理大数据解析还有啥招,聊聊。