はじめに

前回まで、Web Worker の使い方について説明してきました。

今回は、実際に、 Web Worker を使ったプログラムを作成し、動作確認を行っていきたいと思います。

まずは、サンプルプログラムとして、Web Worker を使用しないプログラムを用意し、そのコードをベースに、Web Worker 化しながらすすめていきたいと思います。

題材としては、指定されたディレクトリをまるごと Zip ファイルとして、圧縮するプログラムを使用します。

また、本サンプルプログラムの作成には、外部ライブラリである JSZip ライブラリを使用します。


JSZip ライブラリについて

JSZip(公式サイト)は 2009 年に Stuart Knightley 氏が開発を開始した、ブラウザと Node.js で ZIP を扱うための代表的な JavaScript ライブラリです。

2013 年の v1.0 で安定版となり、2016 年の v3.0 で非同期 API を導入して大きく進化しました。

現在は TypeScript 対応・Streams 対応・安全性強化などを経て、npm で数百万〜数千万規模のダウンロードを持つ実績あるライブラリです。

特徴

  • シンプルで直感的な API
  • 非同期処理(Promise ベース)
  • Node.js Streams 対応
  • TypeScript 対応
  • モジュール構造が明確で拡張性が高い

ライセンス

  • MIT または GPLv3 のデュアルライセンス

開発者の方、関係者の方には、この場をお借りして、深くお礼を申し上げたいと思います。


サンプルプログラムの動作イメージの確認

まずは、Web Worker を使用していないサンプルプログラムを使って実際の動きを確認していただこうと思います。

下記のボタンを押下することで、ポップアップ画面が表示され、サンプルプログラムが起動します。

※ ポップアップ画面外をクリックすると、画面が閉じ、サンプルプログラムは終了します。


サンプルプログラムの概要(かんたん説明)

操作手順

手順①:本頁の「サンプルプログラム起動」を押す。

サンプルプログラムが起動される。 👉 起動時に、保存されたデータがある場合、「取得」が使えるようになります。

手順②:「ファイルを選択」を押す。

ディレクトリ選択ダイアログが表示されるので、ディレクトリを選択し、「アップロード」ボタンを押す。 再確認のポップアップ画面が表示されるので、同じく「アップロード」ボタンを押す。 👉 「アップロード」が完了すると Zip 化対象のファイル一覧が表示されます。

手順③:「Zip 化してダウンロード」を押す。

対象ファイルが Zip 化されます。 👉 Zip 化が完了すると、ダウンロードフォルダに archive.zip の名前でファイルがダウンロードされます。

手順④:必要に応じて、「キャンセル」を押す。

Zip 圧縮をキャンセルします。 👉 このボタンは、「Zip 化してダウンロード」押下後、Zip 圧縮が完了するまでの間だけ押下可能です。


サンプルプログラムのコード紹介

下記に、サンプルプログラムのコードを記します。

※ JSZip ライブラリについては、バージョン変更による互換性のリスク低減の為、ダウンロードした UMD 配布版(v3.10.1)を利用しています。

HTML( index.html )

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>ディレクトリを Zip 化 (Web Worker なし)</title>
  <script src="./lib/jszip.min.js"></script>
  <link rel="stylesheet" href="./style.css">
  <script defer src="./script.js"></script>
</head>
<body>
  <p>
    <input id='input' type="file" webkitdirectory multiple>
    <div id='output'></div>
  </p>
  <button id='zipBtn' disabled>Zip 化してダウンロード</button>
  <button id='cancelBtn' disabled>キャンセル</button>
  <p>
  <div id='status'></div>
  </p>
</body>
</html>

CSS( style.css )

body {
  padding: 1rem;
  background-color: rgb(253, 191, 76);
  text-align: center;
  overflow: hidden;
}
button {
  width: 200px;
}
#output {
  border: 1px solid;
  background-color: skyblue;
  text-align: left;
  width: 100%;
  height: 300px;
  overflow-y: auto;
}
#status {
    font-size: 1.25rem;
    font-weight: bold;
    color: red;
    margin-bottom: 1rem;
}

メインスレッド( script.js )

const input = document.getElementById("input");
const status = document.getElementById("status");
const output = document.getElementById("output");
const zipBtn = document.getElementById("zipBtn");
const cancelBtn = document.getElementById("cancelBtn");

let cancelFlag = false; // Zip 化キャンセルフラグ

input.addEventListener("change", (e) => {
  output.innerHTML = "";
  for (const file of input.files) {
    output.innerHTML += file.webkitRelativePath +"<br>";
  }
  if (input.files.length) status.textContent = "ファイル選択完了";
  zipBtn.disabled = false;
});

// 「Zip 化してダウンロード」ボタン押下時
zipBtn.addEventListener("click", async (e) => {
  zipBtn.disabled = true;
  cancelBtn.disabled = false;
  status.textContent = "圧縮開始";

  const zip = new JSZip();

  for (const file of input.files) {
    // webkitRelativePath には "選択したフォルダ名/サブフォルダ/ファイル名" が入っている
    zip.file(file.webkitRelativePath, file);
  }

  try {
    // Zip化
    const content = await zip.generateAsync(
      { type: "blob" },
      (metadata) => {
        if (cancelFlag) {
          throw new Error("cancelled");
        }
      }
    );
    
    // Zip ファイルのダウンロード
    const url = URL.createObjectURL(content);
    const a = document.createElement("a");
    a.href = url;
    a.download = "archive.zip";
    a.click();
    URL.revokeObjectURL(url);

    status.textContent = "圧縮終了";
    zipBtn.disabled = false;
    cancelBtn.disabled = true;
  } 
  catch (e) {
    if (e.message == "cancelled") {
      cancelBtn.disabled = true;
      status.textContent = "圧縮キャンセル";
      zipBtn.disabled = false;
      cancelFlag = false;
    }
  }
});

// 「キャンセル」ボタン押下時
cancelBtn.addEventListener("click", (e) => {
  cancelFlag = true;
});


「キャンセル」処理の遅延

サンプルプログラムを実際に動かしてみた際、大きなファイルやファイル数が多いディレクトリを Zip 圧縮して「キャンセル」ボタンでキャンセルしようとすると、たまに、有効になるまでにタイムラグがあることが感じられたと思います。

JSZip ライブラリは、2016 年の v3.0 で非同期 API を導入しており、今回使用した generateAsync() 関数でも、イベントループを損なうことがない為、画面が固まるということはありません。

(v2.0 の同期 API の generate() 関数では、完了するまで処理がブロックされ、画面が固まってみえることがありました。)

しかし、キャンセル処理自身が、コールバックを利用したものとなっている為、実際のキャンセル処理の開始に若干影響がでます。これがタイムラグのように感じられることがある原因です。

次回以降では、このサンプルプログラムを Web Worker 化することにより、コードがどの様に変化するのかに加え、この「キャンセル」のタイムラグが解消されるのかについても、みていきたいと思います。

(つづく)