Web Workerを単一ファイルのみで使う! BlobからWorkerを作る方法

プログラミング

こんにちは!

毎週末市場で買った魚をさばいて、週末料理人として魚さばきがかなり上達した、しずかなかずしです。

本日は、JavascriptのWeb Workerという機能を、例によって、ちょっと変わった使い方で調理していきます。

つくるのは、下の動画のようなものです。

sendMessageToWorkerボタンを押すと、Workerで「時間のかかる処理」をバックグランドで実行。終わったら、メインのスレッド(通常のDOMにアクセスできる)にメッセージを送って、終わったことを表示します。

JS Workerを使ったサンプルの動作

それでは、はりきっていってみましょー

JavascriptのWeb Workerとは?

Workerは、ブラウザ内でメインのスレッドとは別のスレッドでJavascriptのコードを実行してくれる機能です。JavaScriptしかやったことない人にとっては、スレッドと言われてもピンとこないかも知れません。他のプログラミング言語ではよくあることですが、複数のプログラムを同時実行する仕組みがあります。それがスレッド。

Workerを使うと、メインの処理と並行して別の処理を同時に実行することが出来る、というものです。

Workerで動作しているスレッドからは、DOMの操作ができない、といった制約がありますが、一方で、UIの処理を停止させること無く、時間のかかる処理をバックグランドで動作させることができます。

使い方は簡単です。

まずは、Workerを作成します。’worker.js’としているJSファイルが、同時実行させる「別の」プログラムです。

let w = new Worker('worker.js')

生成されたWorkerオブジェクトに、postMessage()でメッセージを送ります。この仕組みにより、「別の」プログラムが通知を受けることができます。

以下のコードは送り側のコードです。

    function sendMessageToWorker() {
      w.postMessage('start')
    }

すると、Worker側のJSファイルで登録した、イベントリスナーが発火します。以下のコードは、受け取り側、つまり、Worker()の引数に指定したJSファイル(worker.js)のコードです。postMessage()の引数に渡したものは、リスナーの引数の dataパラメータで受け取ります。

this.addEventListener('message', (m) => {
    console.log(m.data) // 'start' と表示
}

Workerで別ファイルのJSを実行する

では、冒頭の動画で動かしたHTMLを見てみます。

以下のコードがHTMLです。sendMessageToWorkerボタンを押したら、同名の関数が呼ばれて、その中でpostMessage()でWorkerスレッドに「時間のかかる処理」の開始を依頼します。Workerスレッドのオブジェクト(変数w)にaddEventListnerで登録したハンドラーは、Workerスレッドから来るメッセージを受け、その中身をDIVタグの中身に追加していくための画面表示用のコードです(17行目から20行目)。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>Worker trial</title>
  </head>

  <body>
    </div>
    <button onclick="sendMessageToWorker()"> sendMessageToWorker </button>
    <div id='message'>
  </body>

  <script>
    let w = new Worker('worker.js')

    w.addEventListener('message', (m) => {
      let t = document.getElementById('message').innerHTML
      document.getElementById('message').innerHTML = t + m.data + '<br>'
    })

    function sendMessageToWorker() {
      w.postMessage('start')
    }
  </script>
  
</html>

上記のコードにある、Worker('worker.js’)と書かれた部分(15行目)は、Workerスレッドが実行する、外部ファイルworker.jsを指定しています。そのworker.jsの中身を見てみましょう。以下がworker.jsの全コードです。

let count = 0
this.addEventListener('message', (m) => {
    let i = ++count
    this.postMessage(`時間のかかる処理 ${i} はじめます`)
    setTimeout(()=> {
      this.postMessage(`時間のかかる処理 ${i} が終わりました`)
  }, 3000)
})

「時間のかかる処理」の正体は、setTimerによるタイマー処理でした。この処理自体は、特段Workerを使った別スレッドで行う必要は全くないものです。説明のため単に「時間がかかっている感」を出すためにこのような実装にしました。本来の目的としては、Workerスレッドの処理として、例えば、数万桁の円周率の計算のような時間のかかるコードを書くわけです。

thisはこのスレッドの固有コンテキストなので、ブラウザの通常のコンテキストからは独立したものになっています。

WorkerでBlobからJSを実行する

さて、上記のコードだと受け取る側のHTMLファイルと、Workerが生成した別スレッドで実行するJSファイルが別ファイルになっていました。

前回の投稿のように、世の中色々と、単一ファイルで取り回したいこともあります(?)。前回の記事はこちらをご覧ください↓

という訳で、Workerで実行するコードをHTML中に埋め込んでみます。

上記の前回の記事のように、データURLを使う方法もあるのですが、それだと中身のJSのコードが全然読めなくなってしまうので、今回は、Blobという仕組みを使ってみます。

Blobとは、JavaScriptのプログラムの中でファイルのようなデータ(バイナリでもテキストでも)を1つのオブジェクトとして扱う仕組みです。テキストファイルもBlobのオブジェクトとして扱うことができますので、上記のworker.jsのようなファイルをBlobに入れ込んでしまえば良いわけです。

使い方は、JavaScriptのコードをBlobに埋め込むには、以下のような感じ記述します。

let b = new Blog([`console.log('hello, world’)`], {type: 'text/javascript’})

コンストラクタの第2引数では、MIMEタイプを指定しますので、JavaScriptの場合は、’text/javascript’ですね。実際のコードHTMLファイルは以下のようになりました。

  <head>
    <meta charset="utf-8">
    <title>Worker trial</title>
  </head>

  <body>
    </div>
    <button onclick="sendMessageToWorker()"> sendMessageToWorker </button>
    <div id='message'>
  </body>

  <script>

    let worker_js = 
    `
    let count = 0
    this.addEventListener('message', (m) => {
      let i = ++count
      this.postMessage('時間のかかる処理 ' + i + ' はじめます')
      setTimeout(()=> {
        this.postMessage('時間のかかる処理 ' + i + ' が終わりました')
      }, 3000)
    })
    `
    let b = new Blob([worker_js], {type: 'text/javascript'})
    let w = new Worker(URL.createObjectURL(b))

    w.addEventListener('message', (m) => {
      let t = document.getElementById('message').innerHTML
      document.getElementById('message').innerHTML = t + m.data + '<br>'
    })

    function sendMessageToWorker() {
      w.postMessage('start')
    }
  </script>
  
</html>

14行目から24行目が、Workerスレッドで実行するJavaScriptのコードです。「Workerで別ファイルのJSを実行する」のセクションで記述していたworker.jsがそっくりそのまま文字列の変数に入っています。

Workerスレッドに渡すときには、URL.createObjectURL(blob)を実行し、Blobオブジェクトを指し示す「blob:」 schema のURLを生成して(26行目)Workerのコンストラクタに指定するのがポイントですね。

試しにcreateObjectURLの結果をみてみると、こんな感じになっておりました。

“blob:https://shizuka-na-kazushi.style/963c5760-48af-4452-9c36-9f914f11e80c"

カスタムHTMLで埋め込んでみた

Blobを使うとWordPressの「カスタムHTML」ブロックに入れ込むのも簡単です。当サイトはWordPressで作られていますので、ちょっと実際に埋め込んでみます。

実行もできますよ。下の「sendMessageToWorker」と書かれたボタンを押してみて下さい。押してから3秒経ったらWorkerの処理が終わるプログラムです。

本日はここまで!

created by Rinker
¥1,782 (2021/08/05 15:17:45時点 Amazon調べ-詳細)
created by Rinker
¥3,247 (2021/08/05 16:45:12時点 Amazon調べ-詳細)