記事の趣旨
ローカル環境での永続化手法の1つである「Indexed Database API」について,ザックリと勉強したので,そのメモ書き.
1. Indexed Database APIとは
Webブラウザに標準搭載されている,Key-Value型のAPIとのこと.API仕様はW3Cで規定されている.主な登場人物としては「Database」「Object Store」「Record」で,関係性としては以下のとおり.
- Databaseは,0以上のObject Storeを有している
- Object Storeは,Key-Value型のRecordのリストを有している
- Object Storeはnameを持ち,nameはDatabase内で一意であることが保証されている
- Recordのリスト内でkeyは一意であることが保証されている
2. IDBRequest
先述の登場人物に対するCRUDは「Request」を通じて実行される.その仕様はIDBRequest
というインタフェースで定義されている.大雑把に触れると以下のとおり.
-
IDBRequest
の実装オブジェストは,生成された瞬間にリクエストを発出する. - このリクエストは非同期に処理される.
- 結果はオブジェクト内の
result
変数に格納される. - リクエストが成功/失敗したときのイベントハンドラとして
onsucess
およびonerror
が用意されている. - 拡張インタフェースである
IDBOpenDBRequest
のみ,追加でonblock
およびonupgradeneeded
というイベントハンドラが用意されている.後者は初回起動時やDBのバージョン更新時のみ実行される.
たとえばこんな感じ.
var connection;
window.onload = function () {
var openRequest = indexedDB.open('testDB');
openRequest.onsuccess = function () {
connection = openRequest.result;
}
}
indexedDB
はブラウザ側で用意されているオブジェクトなので特に宣言なしに使用できる.こいつのopen('testDB')
メソッドで返却しているのが,まさに先述のIDBOpenRequest
の実装オブジェクト.このメソッド実行後,「testDBという名称のDatabaseに接続する,なければ作る」というリクエストが非同期に実行される.処理が完了するとresult
に結果が格納されているので,それをconnection
変数に渡すよう,onsuccess
のイベントハンドラに設定している.
3. connectionとtransaction
先述のconnectionを起点としてRequestを発出する.connectionは,以下の2つのメソッドを有している.
-
createObjectStore(name)
:新たなObject Storeを作成する. -
transaction(name, mode).objectStore(name)
:既存のObject Storeに対するTransactionを開始する.modeは'readonly'
かreadwrite
.
いずれもIDBObjectStore
の実装オブジェクトを返却してくれる.そのオブジェクトは,例えば以下のようなRequest発出用のメソッドを有している.
-
put(value, key)
:Recordをputする.既存のkeyを使用した場合は上書き. -
add(value, key)
:Recordをaddする.既存のRecordを使用した場合はエラー. -
get(key)
:指定したkeyのRecordをgetする. -
getAll()
:すべてのRecordを配列型でgetする. -
delete(key)
:指定したkeyのRecordをdeleteする. -
clear()
:すべてのRecordをdeleteする.
4. サンプル
ここまでに得た情報を踏まえて,とりあえずザックリとサンプルを書いてみる.Chromeの「79.0.3945.88」で動くことを確認.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<button class="showButton">Object Storeの表示</button>
</body>
<script src="scripts/main.js"></script>
</html>
var connection;
window.onload = function () {
var openRequest = indexedDB.open('testDB');
openRequest.onupgradeneeded = function () {
connection = openRequest.result;
var objectStore = connection.createObjectStore('testObjectStore', {keyPath:'id'});
objectStore.put({id:1, name:'taro'});
objectStore.put({id:2, name:'jiro'});
objectStore.put({id:3, name:'saburo'});
}
openRequest.onsuccess = function () {
connection = openRequest.result;
}
}
function showObjectStore() {
var humans = getAllSync().then(
function(humans) {
for(var i = 0; i<humans.length; i++) {
var human = humans[i];
alert(human.name);
}
}
);
}
function getAllSync() {
return new Promise(
function (resolve, reject) {
var transaction = connection.transaction('testObjectStore', 'readonly');
var request = transaction.objectStore('testObjectStore').getAll();
request.onsuccess = function (e) {
resolve(request.result);
}
request.onerror = reject;
}
)
}
document.querySelector('.showButton').addEventListener('click', showObjectStore);
testDB
内にtestObjectStore
を作り,初期設定として3種類のRecordを格納している.ボタンを押下すると,そのすべてを取得し,alertで表示する,という挙動になる.
細部については,以下のとおり.
-
createObjectStore
メソッドの第二引数{keyPath:'id'}
は,「idをキーと認識します」という宣言のようなもの. - 先ほども述べた通り,リクエスト処理は非同期なので,Promiseによる「待ち」を施している.
- 今回で言うと
transaction.objectStore('testObjectStore').getAll()
がいつ終わるか不明. - なお,
onload
内の各種put
にも,たぶん本当は対策が必要.
- 今回で言うと
- 無事データが取得できたら,resultに配列としてデータが格納されているので,
alert
で全員分の名前を表示.
5. Key Generator
上記の例だと,keyPathで設定したid値を手入力で指定しているが,どうせならそこは自動で設定して欲しいと誰もが思うはず.幸い,「Key Generator」という仕組みが存在する.
用法は簡単で,上記の一部を以下のように書き換えるだけ.
var objectStore = connection.createObjectStore('testObjectStore', {keyPath:'id', autoIncrement:true});
objectStore.put({name:'taro'});
objectStore.put({name:'jiro'});
objectStore.put({name:'saburo'});
put
時にidというkeyがなくなっているが,勝手に生成してくれる.
6. 終わりに
非同期処理の対策をミスると面倒くさいという点を除けば割と便利だと思う.ちょっとした自作ブラウザアプリを作るだけなら,これだけで十分だと思う.