はじめに
Webアプリケーションを開発していると、クライアントサイドでのデータ保存は避けて通れない課題となります。特に以下の制約は多くの開発者を悩ませます:
- LocalStorageはWebWorkerで使用できない
- IndexedDBは単独ではウィンドウをまたいでデータを共有できない
この記事では、これらの制約の詳細と実践的な解決策を解説します。
想定する読者
- WebWorkerを使用したアプリケーションを開発している方
- 複数タブでのデータ同期が必要なアプリケーションを開発している方
- ブラウザストレージの選択に悩んでいる方
前提知識
- JavaScriptの基本的な知識
- WebWorkerの基本的な理解
- ブラウザストレージ(LocalStorage, IndexedDB)の基本的な使用経験
LocalStorageの制約を理解する
WebWorkerでLocalStorageが使えない理由
LocalStorageがWebWorkerで使用できない理由は、以下の2つに集約されます:
-
設計上の制約
- WebWorkerは独立したグローバルコンテキストで動作
- メインスレッドのウィンドウオブジェクトにアクセスできない
-
セキュリティ上の制約
- メインスレッドに影響を与える可能性のあるAPIへのアクセスが制限されている
- DOM操作やLocalStorageなどのウィンドウ関連APIは使用不可
IndexedDBのデータ共有における課題
タブ間でのデータ共有
IndexedDBは強力なクライアントサイドストレージですが、以下の制限があります:
- 組み込みのタブ間通信機能がない
- 自動的なデータ同期の仕組みがない
実践的な解決策
1. WebWorkerでのデータ処理が必要な場合
// IndexedDBを使用したWebWorkerの実装例
// worker.js
self.onmessage = async function(e) {
const db = await openDatabase();
switch(e.data.type) {
case 'save':
await saveData(db, e.data.payload);
self.postMessage({ type: 'saved' });
break;
case 'load':
const data = await loadData(db);
self.postMessage({ type: 'loaded', data });
break;
}
};
async function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open('MyDatabase', 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (e) => {
const db = e.target.result;
db.createObjectStore('myStore', { keyPath: 'id' });
};
});
}
2. タブ間でのデータ同期が必要な場合
以下の2つの方法から選択できます:
A. BroadcastChannel APIの使用
// メインスクリプト内での実装例
const channel = new BroadcastChannel('storage_channel');
// データ更新時
async function updateData(data) {
await saveToIndexedDB(data);
channel.postMessage({
type: 'update',
data: data
});
}
// 他のタブからの更新を受信
channel.onmessage = async (event) => {
if (event.data.type === 'update') {
// UIの更新など
updateUI(event.data.data);
}
};
B. SharedWorkerの使用
// shared-worker.js
let connections = [];
let database = null;
self.onconnect = function(e) {
const port = e.ports[0];
connections.push(port);
port.onmessage = async function(e) {
switch(e.data.type) {
case 'write':
await writeData(e.data.payload);
// 他の接続に変更を通知
broadcast({
type: 'updated',
data: e.data.payload
});
break;
}
};
};
function broadcast(message) {
connections.forEach(port => port.postMessage(message));
}
ユースケース別の選択ガイド
-
シンプルなデータ保存
- WebWorker不要
- タブ間同期必要
→ LocalStorage + BroadcastChannel API
-
複雑なデータ処理
- WebWorker必要
- タブ間同期必要
→ IndexedDB + SharedWorker
または
→ IndexedDB + BroadcastChannel API
-
大量のデータ処理
- WebWorker必要
- 高パフォーマンス要求
→ OPFS in WebWorker
または
→ IndexedDB in WebWorker
パフォーマンスの考慮点
-
LocalStorage
- 読み取り: 約0.0052ミリ秒
- 書き込み: 約0.017ミリ秒
- メインスレッドをブロックする点に注意
-
IndexedDB
- 読み取り: 約0.1ミリ秒
- 書き込み: 約0.17ミリ秒
- 非同期処理でメインスレッドをブロックしない
まとめ
ブラウザストレージの選択は、アプリケーションの要件によって大きく変わります:
- シンプルなデータ保存ならLocalStorage
- 複雑なデータ処理や大量データならIndexedDB
- タブ間同期が必要な場合はBroadcastChannelかSharedWorker
これらの制約を理解し、適切なストレージと同期方法を選択することで、効率的なWebアプリケーションの開発が可能になります。