IndexedDBとエラーハンドリングのベストプラクティス: 安全で簡潔な設計方法
はじめに
JavaScript のクライアントサイドデータベースである IndexedDB
は、ブラウザ内で大量のデータを永続化するための強力な API です。しかし、その利用には注意が必要です。特に、IndexedDB の非同期操作に伴うエラーハンドリングは慎重に設計しないと、バグの原因やデバッグの難しさに繋がります。
この記事では、IndexedDB
の基本的な使い方と、エラー発生時の適切なハンドリング方法について解説します。簡潔で安全なエラーハンドリングを実現するためのコード例も紹介します。
IndexedDB とは?
IndexedDB
は、クライアント側で大量の構造化データを保存するためのブラウザ組み込みのデータベース API です。特徴として以下の点があります:
- キーと値でデータを保存する: データは「オブジェクトストア」と呼ばれる単位で保存されます。
- 非同期 API: IndexedDB の操作は非同期で行われるため、Promise や async/await を活用して扱う必要があります。
- 永続的なストレージ: 一度保存したデータは、ブラウザが閉じられても保持されます。
IndexedDB を使った基本操作の流れ
-
データベースを開く:
- データベースを開く際に
open
メソッドを使用します。 - 非同期でデータベース接続が完了するまで待つ必要があります。
- データベースを開く際に
-
トランザクションを作成する:
- データベース操作はすべてトランザクションを通じて行われます。
readonly
またはreadwrite
モードを指定します。
- データベース操作はすべてトランザクションを通じて行われます。
-
オブジェクトストアを操作する:
- トランザクションの
objectStore
メソッドを使ってデータの取得や保存を行います。
- トランザクションの
-
エラーハンドリング:
- IndexedDB の操作中にエラーが発生する可能性があるため、適切なエラーハンドリングを実装することが重要です。
IndexedDB のエラーハンドリング: よくある課題
IndexedDB のエラーが発生する可能性のある箇所
- データベースのオープン失敗:
openDatabase()
メソッドが失敗する場合、たとえばブラウザが IndexedDB をサポートしていない、あるいはストレージ制限を超えているケース。 - トランザクションエラー:
存在しないオブジェクトストアを操作しようとした場合や、権限がない場合。 - データ取得エラー:
存在しない ID を指定してデータを取得しようとした場合。
実際のコード例: データ取得関数とエラーハンドリング
以下に、IndexedDB
を使ったデータ取得関数のコード例を示します。この関数では、エラーを適切にログに記録しつつ、呼び出し元がシンプルに扱えるような設計を採用しています。
データ取得関数: getIDB
async function getIDB(table, id) {
try {
const db = await openDatabase(); // IndexedDB のデータベースを開く
const transaction = db.transaction([table], 'readonly'); // 読み取り専用トランザクションを作成
const objectStore = transaction.objectStore(table); // オブジェクトストアを取得
const getRequest = objectStore.get(id); // ID を指定してデータを取得
return new Promise((resolve) => {
getRequest.onsuccess = (event) => {
resolve(event.target.result); // データが存在する場合は返す
};
getRequest.onerror = (event) => {
console.error('Request error:', event.target.error, table, id);
resolve(null); // エラー発生時は null を返す
};
});
} catch (error) {
console.error('Unexpected error in getIDB:', error, table, id);
return null; // 致命的なエラーが発生した場合は null を返す
}
}
コード解説
1. openDatabase
メソッド
データベースを開く際、通常はエラーが発生しませんが、ブラウザが IndexedDB をサポートしていない場合や、データベース接続が失敗した場合に例外がスローされます。
const db = await openDatabase();
2. トランザクション作成
db.transaction()
を使って読み取り専用のトランザクションを作成します。操作対象のオブジェクトストアを指定します。
const transaction = db.transaction([table], 'readonly');
const objectStore = transaction.objectStore(table);
3. データ取得
objectStore.get(id)
を使って指定した ID のデータを取得します。ここでエラーが発生した場合は、getRequest.onerror
が呼び出されます。
const getRequest = objectStore.get(id);
4. エラーハンドリング
-
getRequest.onerror
:
データの取得中にエラーが発生した場合、このハンドラが呼び出されます。ログを記録し、エラーが発生した場合でも処理を継続できるようにresolve(null)
を返します。 -
catch (error)
:
IndexedDB が利用できないなど致命的なエラーが発生した場合、このcatch
ブロックで処理されます。同様にconsole.error
でログを記録しつつ、呼び出し元で簡単にエラーを処理できるようにnull
を返します。
エラーを意図的に発生させる方法
テスト時にエラーを意図的に発生させるには、以下の方法を試すことができます:
-
存在しないオブジェクトストアを指定:
const data = await getIDB('nonexistentStore', '123');
-
不正な ID を指定: 存在しない ID を指定すると
onsuccess
が呼ばれ、result
にnull
が返ります(エラーにはなりません)。 -
openDatabase の失敗をシミュレート:
openDatabase
をモック化して意図的にエラーをスローします:async function openDatabase() { throw new Error('Simulated openDatabase error'); }
呼び出し元での使用例
上記の getIDB
関数を使ってデータを取得する例を示します:
async function main() {
const data = await getIDB('users', 'user123');
if (data) {
console.log('データを取得しました:', data);
} else {
console.log('データが見つかりませんでした');
}
}
main();
まとめ
IndexedDB を使ったアプリケーションでは、エラーを適切にハンドリングすることが非常に重要です。本記事で紹介した getIDB
関数は、以下の特徴を持つシンプルかつ安全な設計になっています:
- 非同期操作を Promise ベースで簡潔に処理。
- 致命的なエラーをログに記録しつつ、呼び出し元に影響を与えない設計。
- エラー発生時でも
null
を返して処理を継続可能。
IndexedDB を利用する際は、このように安全性と簡潔さを両立する設計を目指しましょう!
登録日:
更新日: