在現代前端項目中使用 Worker
由於需要在 Browser 進行大量的(音頻轉解碼)計算,所以吾輩開始嘗試使用 webworker 分離 CPU 密集型的計算操作,最終找到了 comlink 這個庫,但之前在 vue 中使用時發生了錯誤,目前看起來已經得到了解決,所以在此記錄一下。
調研方案
- web-worker-proxy :結合了 proxy/promise/webworker 的強大工具庫,但如何在 ts 中使用卻是個問題
- Orc.js :一個簡單的 worker 封裝
- VueWorker :結合 vue 的 worker 封裝,無法理解,難道真的會有人在 vue 組件中進行大量計算麼?
- comlink:Chrome 的一個基於 proxy/promise/webworker 的封裝庫
- worker-plugin :和上面的同屬 chrome 實驗室的一個 webpack 插件
最後決定使用 comlink 結合 worker-plugin 實現簡單的 worker 使用。
安裝與配置
在 GitHub 上有 可運行示例 demo
相關問題: comlink-loader 工作不正常
添加相關依賴
yarn add comlink yarn add -D worker-plugin
在 webpack 配置中添加插件
{ plugins: [new WorkerPlugin()] }
這裏一般不需要特殊參數配置,如果需要,可以參考: worker-plugin
示例
基本示例
添加一個簡單的 hello.worker.ts
import { expose } from 'comlink' const obj = { counter: 0, inc() { this.counter++ }, } expose(obj)
在 main.ts
中使用
const obj = wrap(new Worker('./hello.worker.ts', { type: 'module' })) as any alert(`Counter: ${await obj.counter}`) await obj.inc() alert(`Counter: ${await obj.counter}`)
但這裏並不是類型安全的,所以我們可以實現正確的類型。
添加一個 hello.worker.ts
暴露出來的類型 HelloWorkerType
export interface HelloWorkerType { counter: number inc(): void }
同時爲了支持在 main.ts 中使用正確的類型,需要使用泛型
main.ts 修改如下
const obj = wrap<HelloWorkerType>( new Worker('./hello.worker.ts', { type: 'module' }), ) alert(`Counter: ${await obj.counter}`) await obj.inc() alert(`Counter: ${await obj.counter}`)
純函數
聲明函數的類型 HelloCallback.worker.type.d.ts
type ListItem<T extends any[]> = T extends (infer U)[] ? U : never export type MapWorkerType = <List extends any[], U>( arr: List, cb: (val: ListItem<List>) => U | Promise<U>, ) => Promise<U[]>
聲明一個純函數 HelloCallback.worker.ts
import { MapWorkerType } from './HelloCallback.worker.type' import { expose } from 'comlink' export const map: MapWorkerType = (arr, cb) => Promise.all(arr.map(cb)) expose(map)
注:此處最好使用變量的形式,主要是爲了方便將函數類型剝離出去。
在 main.ts 中使用
const map = wrap<MapWorkerType>( new Worker('./HelloCallback.worker.ts', { type: 'module', }), ) const list = await map( [1, 2, 3], proxy((i) => i * 2), ) console.log('list: ', list)
使用 class 的形式
聲明接口 HelloClass.worker.type.d.ts
export class HelloClassWorker { sum(...args: number[]): number }
worker 文件 HelloClass.worker.ts
import { HelloClassWorker } from './HelloClass.worker.type' import { expose } from 'comlink' class HelloClassWorkerImpl implements HelloClassWorker { sum(...args: number[]): number { return args.reduce((res, i) => res + i, 0) } } expose(HelloClassWorkerImpl)
關於此處 implements class
的問題,吾輩偶然一試之下沒報錯也很奇怪,所以找到了相關問題 Typescript: How to extend two classes?
,官方文檔也同樣說明了這個特性 Mixins
。
在 main.ts 中使用
const HelloClassWorkerClazz = wrap<typeof HelloClassWorker>( new Worker('./HelloClass.worker.ts', { type: 'module', }), ) const instance = await new HelloClassWorkerClazz() console.log(await instance.sum(1, 2))
總結
總的來說,使用 worker 的基本分三步
- 編寫需要放在 worker 裏內容的類型定義
- 根據類型定義實現它
- 在主進程的代碼中使用它
注:當然,如果是複雜的東西,可以直接在單獨的文件中實現,然後聲明一個 .worker.ts 暴露出去,不在 .worker.ts 中包含任何