Web Workerとは
JavaScriptはシングルスレッド。
シングルスレッドで重たい処理をさせると、処理が終わるまでブラウザの操作や描画が止まる。
でも、ウェブワーカーという仕組みを使うとバックグラウンドで処理できる。
つまり、重たい処理をしたいときにいいかんじになるっぽい。
こうした
export const webWorkerAsync = <T, S>(task: (arg: MessageEvent<T>) => S, arg: T): Promise<S> => {
return new Promise((resolve, reject) => {
const taskString = `
const task = ${task.toString()};
this.onmessage = (arg) => {
postMessage(task(arg));
}
`;
const blob = new Blob([taskString], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
worker.addEventListener('message', (m: MessageEvent<S>) => {
resolve(m.data);
});
worker.postMessage(arg);
});
};
使い方
// WebWorkerに登録する、重たい関数を書く
const task = (arg) => {
// 重たい処理
return arg;
}
// ↑に渡る変数
let arg = 'hoge';
webWorkerAsync(task, arg).then(done => {
console.log(done)
// 'hoge'
});
補足
webWorkerAsync() の引数
Web Worker
は、メッセージ
と呼ばれるシステムでメインスレッドとバックグラウンドスレッドのやりとりを行う。
そのため、大まかな段取りとしては、次のようになる。
- 処理の登録
- メインスレッドからバックグラウンドにメッセージを送信
- バックグラウンドがメッセージを受け取り、登録された処理を開始
- バックグランドの処理が終わったら、メインスレッドへメッセージを送信
- メインスレッドがメッセージを受け取る
故に実行する関数と、その関数に渡す変数は別々に持たせたい。
webWorkerAsyncの第一引数が実行する関数(1)で、第二引数が関数に渡される変数(2)になっている。
T,S
Tは関数に渡す引数の型
SはPromiseがresolveしたら帰ってくる値の型
工夫したこと
Blob
Web Worker
は通常こんなかんじで、外部ファイルを読み込ませて使うらしい。
const myWorker = new Worker("worker.js");
そんなことを言われても、大体Reactやらなんやらnode.jsでトランスパイルする形でコードを書くわけで、ファイルを分割するのは面倒臭い。
ので、テキストをBlob URLを使った。
task.toString()
というころで、Blobに渡す処理は文字列にしたい
(文字列にしなくてもいいのかもしれんが、Blobは雰囲気で使っておりわからん)
文字列としてJSを書いても動くが、TSの恩恵も預かりたいので、普通に関数を書いて、toStringで関数の中身を文字列に変換する形を取った。
Promise
とにかくPromiseにしたかった。
その他
ジェネリクス、どう・・・?
最近勉強しはじめたのだが、もっといい書き方がある気がしてならない・・・。
ちなみにこれをReactでそのまま使うと、処理が複数回呼ばれてしまうので、その辺は別途一度しか呼ばれないようにする工夫が必要なので注意。