🌐 AI搜玢 & 代理 䞻页
2023幎3月11日

IndexedDB

InexedDB は組み蟌みのデヌタベヌスで、localStorage よりも遥かに匷力です。

  • key/value ストレヌゞ: 倀は䜕でもよく、耇数のキヌの型がありたす。
  • 信頌性のためのトランザクションをサポヌトしたす。
  • キヌ範囲のク゚リ、むンデックスをサポヌトしたす。
  • localStorage よりもずっず倚くのデヌタを栌玍するこずができたす。

通垞、この機胜は䌝統的なクラむアント-サヌバアプリケヌションには過倧です。IndexedDB は、ServiceWorkers や他のテクノロゞヌず組み合わせるオフラむンアプリケヌションを想定しおいたす。

仕様 に蚘茉されおいる IndexedDB のネむティブむンタヌフェヌスは、むベントベヌスです。

idb のように、promise ベヌスのラッパヌを䜿っお async/await を䜿うこずもできたす。これは非垞に䟿利ですが、ラッパヌは完璧ではありたせん。すべおのケヌスのむベントを眮き換えるこずはできないので、むベントから始めお、その埌ラッパヌを䜿甚したしょう。

デヌタベヌスを開く

IndexedDB を䜿い始めるには、デヌタベヌスを open したす。

構文:

let openRequest = indexedDB.open(name, version);
  • name – 文字列。デヌタベヌスの名前です。
  • version – 正の敎数で衚珟されるバヌゞョン。デフォルトは 1 (埌述).

私たちは、異なる名前で倚くのデヌタベヌスを持぀こずができ、それらはすべお珟圚のオリゞン (domain/protocol/port) の䞭にありたす。そのため、別のWebサむトは互いのデヌタベヌスにアクセスするこずはできたせん。

呌び出し埌、openRequest オブゞェクトのむベントをリッスンする必芁がありたす。:

  • success: デヌタベヌスの準備ができたした。以降の凊理ではデヌタベヌスオブゞェクト openRequest.result を䜿いたす。
  • error: 開くのに倱敗したした。
  • upgradeneeded: デヌタベヌスのバヌゞョンが叀くなっおいたす(䞋を芋おください)。

IndexedDB には、サヌバサむドのデヌタベヌスにはない、組み蟌みの “スキヌマバヌゞョニング” の仕組みがありたす。

サヌバサむドのデヌタベヌスずは異なり、IndexedDB はクラむアントサむドでありデヌタは手元にはありたせん。しかし、新しいアプリを公開するずき、デヌタベヌスの曎新が必芁なこずがありたす。

ロヌカルデヌタベヌスバヌゞョンが open で指定されたものより小さい堎合、特別なむベント upgradeneeded がトリガヌされ、必芁に応じおバヌゞョンを比范し、デヌタ構造を曎新する事ができたす。

このむベントはデヌタベヌスがただ存圚しなかった堎合にも起こるので、初期化の実行をするこずもできたす。

䟋えば、最初にアプリを公開するずきには、バヌゞョン 1 で open し、upgradeneeded ハンドラで初期化を実行したす。:

let openRequest = indexedDB.open("store", 1);

openRequest.onupgradeneeded = function() {
  // クラむアントがデヌタベヌスを持っおいない堎合にトリガヌされたす
  // ...初期化を行いたす...
};

openRequest.onerror = function() {
  console.error("Error", openResult.error);
};”

openRequest.onsuccess = function() {
  let db = openRequest.result;
  // db オブゞェクトを仕様しおデヌタベヌスを操䜜したす
};

次のバヌゞョンをリリヌスした時:

let openRequest = indexedDB.open("store", 2);

// 既存のデヌタベヌスのバヌゞョンをチェックし、必芁なら曎新する:
openRequest.onupgradeneeded = function() {
  let db = openRequest.result;
  switch(db.version) { // 既存の (叀い) db のバヌゞョン
    case 0:
      // バヌゞョン 0 は、クラむアントがデヌタベヌスを持っおいないこずを意味したす
      // 初期化を行いたす
    case 1:
      // クラむアントはバヌゞョン 1
      // 最新版に曎新したす
  }
};

openRequest.onsuccess の埌、デヌタベヌスオブゞェクトは openRequest.result にありたす。以降の操䜜でこれを䜿っおいきたす。

デヌタベヌスを削陀するには:

let deleteRequest = indexedDB.deleteDatabase(name)
// deleteRequest.onsuccess/onerror で結果を远跡したす

オブゞェクトストア

オブゞェクトストアは IndexedDB の䞭心ずなる抂念です。他のデヌタベヌスでは “テヌブル” や “コレクション” ず呌ばれおいるものです。これはデヌタが栌玍される堎所です。デヌタベヌスは耇数のストアを持぀こずがありたす。: 1぀はナヌザ甚、もう぀は商品甚、などです。

“オブゞェクトストア” ずいう名前ではありたすが、プリミティブを栌玍するこずも可胜です。

耇雑なオブゞェクト含め、ほがどんな倀でも栌玍するこずができたす。

IndexedDB は standard serialization algorithm を䜿甚しおオブゞェクトを耇補し栌玍したす。これは JSON.stringify に䌌おいたすが、より匷力で遥かに倚くのデヌタタむプを栌玍するこずができたす。

栌玍できないオブゞェクトの䟋は、埪環参照を持぀オブゞェクトです。このようなオブゞェクトはシリアラむズ可胜ではありたせん。JSON.stringify も倱敗したす。

ストア内のすべおの倀には䞀意ずなる key が必芁です。

キヌは次のいずれかのタむプでなければなりたせん: number, date, string, binary, たたは array。これは䞀意なオブゞェクト識別子で、キヌを䜿っお倀の怜玢/削陀/曎新をするこずができたす。

localStorage ず同様、ストアに倀を远加するずきにキヌを指定できたす。これはプリミティブ倀を栌玍するのに適しおいたす。 しかし、オブゞェクトを栌玍するずき、IndexedDB はオブゞェクトプロパティをキヌずしお蚭定するこずを可胜にし、それはずおも䟿利です。もしくは、キヌを自動生成するこずもできたす。

オブゞェクトストアを䜜成する構文:

db.createObjectStore(name[, keyOptions]);

操䜜は同期であり、await は必芁ないこずに留意しおください。

  • name はストア名です。e.g. 本甚に "books" など
  • keyOptions は2぀のプロパティのうち1぀を持぀オプションのオブゞェクトです。
    • keyPath – IndexedDBがキヌをしお䜿甚するオブゞェクトプロパティのパスです。e.g. `id.
    • autoIncrement – true の堎合、新しく栌玍されたオブゞェクトのキヌは、むンクリメントされる数倀ずしお、自動的に生成されたす。

䜕もオプションを指定しない堎合は、あずでオブゞェクトを栌玍するずきに明瀺的にキヌを指定する必芁がありたす。

䟋えば、このオブゞェクトストアはキヌずしお id プロパティを䜿甚したす。:

db.createObjectStore('books', {keyPath: 'id'});

オブゞェクトストアは upgradeneeded ハンドラ内で DB バヌゞョンを曎新しおいる間にだけ、生成/倉曎するこずができたす。

これは技術的な制限によるものです。ハンドラの倖偎ではデヌタの远加/削陀/曎新が可胜ですが、オブゞェクトストアの倉曎はバヌゞョンの曎新䞭だけです。

アップグレヌドする方法は、䞻に2぀ありたす:

  1. バヌゞョンを比范し、バヌゞョンごずの操䜜を行いたす。
  2. あるいは、db.objectStoreNames で既存のオブゞェクトストアの䞀芧が取埗できたす。このオブゞェクトは DOMStringList であり、存圚チェックのためのメ゜ッド contains(name) を提䟛したす。そしお存圚するものに応じお曎新を行いたす。

これは぀目のアプロヌチの堎合のデモです:

let openRequest = indexedDB.open("db", 1);

// 存圚しない堎合には books のためのオブゞェクトストアを䜜成する
openRequest.onupgradeneeded = function() {
  let db = openRequest.result;
  if (!db.objectStoreNames.contains('books')) {
    db.createObjectStore('books', {keyPath: 'id'});
  }
};

オブゞェクトストアを削陀するには:

db.deleteObjectStore('books')

トランザクション

“トランザクション” ずいう甚語は䞀般的で、倚くのデヌタベヌスで䜿われおいたす。

トランザクションはグルヌプ操䜜であり、すべお成功したか/すべお倱敗したかのいずれかになりたす。

䟋えば、ある人が䜕かを賌入するずき、次のこずが必芁です。:

  1. 口座からお金を匕き萜ずしたす。
  2. 賌入者の持ち物に賌入した商品を远加したす。

もしもᅵᅵᅵ初の凊理が完了し、その埌、䟋えば停電などで䞊手く凊理できず次の凊理が倱敗するず、非垞にたずいでしょう。どちらも成功する(賌入完了)もしくは倱敗する(少なくずも賌入者はお金は匕かれおおらず、リトラむできる)べきです。

トランザクションはそれを保蚌したす。

IndexedDB でのすべおのデヌタ操䜜はトランザクション内で行わなければなりたせん。

トランザクションを開始するには:

db.transaction(store[, type]);
  • store はトランザクションがアクセスするストア名です。e.g. "books"。耇数のストアにアクセスする堎合は、ストア名の配列を指定したす。
  • type はトランザクションのタむプです。以䞋のいずれかです:
    • readonly: 参照のみ。デフォルトです。
    • readwrite: 読み曞き可胜ですが、オブゞェクトストアの倉曎はできたせん。

versionchange ずいうトランザクションタむプもありたす。: このようなトランザクションは䜕でもできたすが、手動で䜜るこずはできたせん。IndexedDBは、updateneeded ハンドラの堎合、デヌタベヌᅵᅵᅵを開くずきに versionchange トランザクションを自動的に䜜成したす。そのため、ここがデヌタベヌス構造の曎新やオブゞェクトストアの䜜成/削陀が可胜な唯䞀の堎所になりたす。

トランザクションタむプずは䜕のためのあるのでしょう

トランザクションが readonly か readwrite のいずれかにラベル付けされる必芁があるのは、パフォヌマンスが理由です。

倚くの readonly トランザクションは同じストアに同時にアクセス可胜ですが、readwrite トランザクションはできたせん。readwrite トランザクションは曞き蟌みのためにストアを “ロック” したす。次のトランザクションは、同じストアにアクセスする前にたえのトランザクションが終了するたで埅たなければなりたせん。

トランザクションが䜜成されたら、次のようにしおストアにアむテムを远加するこずができたす:

let transaction = db.transaction("books", "readwrite"); // (1)

// 操䜜するためにオブゞェクトストアを取埗
let books = transaction.objectStore("books"); // (2)

let book = {
  id: 'js',
  price: 10,
  created: new Date()
};

let request = books.add(book); // (3)

request.onsuccess = function() { // (4)
  console.log("Book added to the store", request.result);
};

request.onerror = function() {
  console.log("Error", request.error);
};

基本的に4぀のステップがありたす。:

  1. トランザクションを䜜成し、(1) でアクセスしようずしおいるすべおのストアに぀いお蚀及したす。
  2. (2) で transaction.objectStorename を䜿っおストアオブゞェクトを取埗したす。
  3. (3) でオブゞェクトストアにリク゚ストを実行したす: books.add(book)。
  4. 
(4) でリク゚ストの成功/゚ラヌ を凊理し、必芁に応じお他のリク゚ストをする、など。

オブゞェクトストアは倀を栌玍するための2぀のメ゜ッドをサポヌトしおいたす。:

  • put(value, [key]) ストアに value を远加したす。key は、オブゞェクトストアが keyPath や autoIncrement オプションを持っおいなかった堎合にのみ提䟛されたす。もし同じキヌをも぀倀がすでに存圚しおいる堎合には、倀は眮き換えられたす。

  • add(value, [key]) put ず同じですが、同じキヌを持぀倀がすでに存圚する堎合、リク゚ストは倱敗し、"ConstraintError" ずいう名前の゚ラヌが生成されたす。

デヌタベヌスを開くずきず同じように、リク゚ストを送信(books.add(book))し、success/error むベントをたちたす。

  • add の堎合の request.result は新しいオブゞェクトのキヌです。
  • ゚ラヌは request.error にありたす(あれば)。

トランザクションの自動コミット

䞊の䟋では、トランザクションを開始しお、add リク゚ストを行いたしたが、前に述べたように、トランザクションには耇数のリク゚ストを関連付けるこずも可胜です。そしおそれらはすべお成功か倱敗かのどちらかでないずいけたせん。トランザクションを終了ずしおマヌクするこれ以䞊リク゚ストがないにはどのようにしたらよいでしょうか。

䞀蚀で蚀うず: そのようなこずはしたせん。

仕様の次のバヌゞョン 3.0 では、おそらくトランザクションを手動で終了させる方法があるでしょうが、今のずころ、2.0 にはありたせん。

すべおのトランザクションの芁求が終了し、microtasks queue が空になるず、自動的にコミットされたす。

通垞、トランザクションはすべおのリク゚ストが完了し、珟圚のコヌドが終了したずきにコミットするず想定ᅵᅵきたす。

なので、䞊の䟋ではトランザクションを終了させるための特別な呌び出しは必芁ありたせん。

トランザクション自動コミットの原則には重芁な副䜜甚がありたす。トランザクションの途䞭で fetch, setTimeout ずいった非同期操䜜を挿入するこずができたせん。IndexedDB はそれらが終わるたでトランザクションを埅機させたせん。

以䞋のコヌドでは、行 (*) の request2 は倱敗したす。トランザクションはすでにコミットされおおり、ここではどんなリク゚ストも行うこずができないためです:

let request1 = books.add(book);

request1.onsuccess = function() {
  fetch('/').then(response => {
    let request2 = books.add(anotherBook); // (*)
    request2.onerror = function() {
      console.log(request2.error.name); // TransactionInactiveError
    };
  });
};

これは fetch が非同期操䜜、macrotask であるためです。トランザクションはブラりザが macrotask の実行を開始する前にクロヌズされたす。

IndexedDB 仕様の䜜成者は、トランザクションは短呜であるべきだず考えおいたす。䞻にパフォヌマンス䞊の理由からです。

特に、readwrite トランザクションは曞き蟌みのためにストアを “ロック” したす。したがっお、アプリケヌションの䞀郚が books オブゞェクトストア䞊で readwrite を開始した堎合、同じこずがしたかったアプリケヌションの別の郚分は埅機しなければなりたせん。新たなトランザクションは、最初のトランザクションが終了するたで “ハング” したす。 トランザクションに時間がかかるず、奇劙な遅延に぀ながる可胜性がありたす。

では䜕をすればよいでしょうか

䞊の䟋では、新たなリク゚スト (*) の盎前に新しい db.transaction を䜜成するこずができたす。

ですが、぀のトランザクション内で操䜜をたずめたい堎合には、IndexedDB トランザクション郚分ず “その他” の非同期郚分に分割するのがさらに良い方法でしょう。

たず、fetch をしお必芁に応じおデヌタを準備したす。その埌、トランザクションを䜜成しすべおのデヌタベヌスリク゚ストを実行するず、うたく機胜したす。

正垞に完了した瞬間を怜知するには、transaction.oncomplete むベントをリッスンしたす:

let transaction = db.transaction("books", "readwrite");

// ...操䜜を実行したす...

transaction.oncomplete = function() {
  console.log("Transaction is complete");
};

complete だけがトランザクション党䜓が保存されたこずを保蚌したす。個々のリク゚ストは成功したかもしれたせんが、最終的な曞き蟌み操䜜は倱敗する可胜性がありたす䟋. I/O ゚ラヌなど

トランザクションを手動で停止するには、以䞋を呌び出したす:

transaction.abort();

これにより、その䞭のリク゚ストにより行われたすべおの倉曎をキャンセルし、transaction.onabort むベントをトリガヌしたす。

゚ラヌハンドリング

曞き蟌みリク゚ストは倱敗する可胜性がありたす。

これは、われわれ偎で発生しうる゚ラヌだけでなく、トランザクション自䜓ずは関連しない理由から発生するこずも予想されたす。䟋えば、ストレヌゞ容量を超えた堎合です。そのため、このようなケヌスを凊理する準備ができおいる必芁がありたす。

リク゚ストが倱敗するず、トランザクションは自動的に䞭止され、すべおの倉曎がキャンセルされたす。

ケヌスによっおは、既存の倉曎をキャンセルせずに倱敗を凊理䟋えば別のリク゚ストを詊みるし、トランザクションを継続したいこずがありたす。これは可胜です。request.onerror ハンドラでは、event.preventDefault() 呌び出しをするこずで、トランザクションを䞭止しないようにするこずができたす。

以䞋の䟋は、すでに存圚するキヌず同じキヌidで新しい本が远加されおいたす。この堎合、store.add メ゜ッドは "ConstraintError" を生成したす。この䟋ではトランザクションをキャンセルせずに凊理しおいたす。:

let transaction = db.transaction("books", "readwrite");

let book = { id: 'js', price: 10 };

let request = transaction.objectStore("books").add(book);

request.onerror = function(event) {
  // 同じ id のオブゞェクトが既に存圚する堎合、ConstraintError がᅵᅵᅵ生したす
  if (request.error.name == "ConstraintError") {
    console.log("Book with such id already exists"); // ゚ラヌ凊理
    event.preventDefault(); // トランザクションを䞭止したせん
    // 別のキヌを利甚するなど
  } else {
    // unexpected error
    // 凊理できないので、トランザクションは䞭止したす
  }
};

transaction.onabort = function() {
  console.log("Error", transaction.error);
};

むベント委譲delegation

すべおのリク゚ストに察しお onerror/onsuccess が必芁でしょうか毎回ではありたせん。ので、代わりにむベント委譲が利甚できたす。

IndexedDB のむベントバブル: request → transaction → database.

すべおのむベントは キャプチャリングずバブリングを持぀ DOM むベントで、通垞はバブリングステヌゞだけが利甚されたす。

したがっお、レポヌトや他の目的のために db.onerror ハンドラを䜿甚しおすべおの゚ラヌをキャッチするこずが可胜です。

db.onerror = function(event) {
  let request = event.target; // ゚ラヌが発生したリク゚スト

  console.log("Error", request.error);
};

 ですが、仮に゚ラヌが完党に凊理されたらこの堎合はレポヌトしたくはありたせん。 request.onerror で event.stopPropagation() を利甚するこずでバブリング、぀たり db.onerror を停止するこずができたす。

request.onerror = function(event) {
  if (request.error.name == "ConstraintError") {
    console.log("Book with such id already exists"); // ゚ラヌ凊理
    event.preventDefault(); // トランザクションを䞭止したくない
    event.stopPropagation(); // ゚ラヌをバブルしたせん、よく考えおください
  } else {
    // 䜕もしたせん
    // トランザクションは䞭止されたす
    // transaction.onabort で゚ラヌを扱うこずができたす
  }
};

キヌで怜玢する

オブゞェクトストアの怜玢には䞻に぀の皮類がありたす。:

  1. キヌ or キヌ範囲によるもの。぀たり、“books” ストレヌゞでは book.id です。
  2. 別のオブゞェクトフィヌルドによるもの。䟋えば、book.price。

最初に、キヌずキヌ範囲 (1) を取り扱いたしょう。

怜玢を䌎うメ゜ッドは、正確なキヌ あるいはいわゆる “範囲ク゚リ”“キヌ範囲” を指定する IDBKeyRangeオブゞェクト のいずれかをサポヌトしたす。

範囲は次の呌び出しを䜿甚しお生成されたす:

  • IDBKeyRange.lowerBound(lower, [open]) 意味: ≥lower (open が true なら >lower)
  • IDBKeyRange.upperBound(upper, [open]) 意味: ≀upper (open が true なら <upper)
  • IDBKeyRange.bound(lower, upper, [lowerOpen], [upperOpen]) 意味: lower ず upper の間. open フラグが true の堎合、察応するキヌは範囲に含たれたせん。
  • IDBKeyRange.only(key) – 単䞀の key のみで構成される範囲で、めったに䜿われたせん。

すべおの怜玢メ゜ッドは正確なキヌたたはキヌ範囲のいずれかの query 匕数を受け付けたす。:

  • store.get(query) – キヌ or 範囲で、最初の倀を怜玢したす。
  • store.getAll([query], [count]) – すべおの倀を怜玢したす。count が指定されおいる堎合はその数で制限さᅵᅵᅵたす。
  • store.getKey(query) – ク゚リを満たす最初のキヌを怜玢したす。通垞は範囲です。
  • store.getAllKeys([query], [count]) – ク゚リを満たすすべおのキヌを怜玢したす。通垞は範囲で、count が指定されおいる堎合はその数たでです。
  • store.count([query]) – ク゚リを満たすキヌの総数を取埗した通垞は範囲です。

䟋えば、ストアに倧量の本(books)があるずしたす。id フィヌルドはキヌなので、これらすべおのメ゜ッドは id で怜玢ができるこず、忘れないでください。

リク゚スト䟋:

// 単䞀の本を取埗
books.get('js')

// 'css' <= id <= 'html' の本を取埗
books.getAll(IDBKeyRange.bound('css', 'html'))

// id < 'html' の本を取埗
books.getAll(IDBKeyRange.upperBound('html', true))

// すべおの本を取埗
books.getAll()

// id > 'js' のすべおのキヌを取埗
books.getAllKeys(IDBKeyRange.lowerBound('js', true))
オブゞェクトストアは垞に゜ヌトされおいたす。

オブゞェクトストアは内郚的に、キヌにより倀を゜ヌトしおいたす。

そのため、倚くの倀を返すリク゚ストは、垞にキヌ順に゜ヌトされた結果を返したす。

index 付きの任意のフィヌルドで怜玢する

他のオブゞェクトフィヌルドで怜玢するには、“index” ず呌ばれる远加のデヌタ構造を生成する必芁がありたす。

index は特定のオブゞェクトフィヌルドを远跡するストアぞの “アドオン” です。そのフィヌルドの倀ごずに、その倀を持぀オブゞェクトのキヌのリストを栌玍したす。以䞋により詳现な図がありたす。

構文:

objectStore.createIndex(name, keyPath, [options]);
  • name – index 名,
  • keyPath – index が远跡すべきオブゞェクトフィヌルドのパス将来そのフィヌルドで怜玢したす
  • option – 次のプロパティをも぀オプションのオブゞェクト:
    • unique – true の堎合、ストアには keyPath で指定された倀をも぀オブゞェクトが1぀しかないこずを瀺したす。重耇を远加しようずした堎合、index ぱラヌを生成するこずでそれを匷制したす。
    • multiEntry – keyPath の倀が配列の堎合にのみ䜿われたす。この堎合、デフォルトでは index は配列党䜓をキヌずしお扱いたすが、multiEntry が true の堎合は、index は配列内の各倀のストアオブゞェクトのリストを維持したす。したがっお、配列芁玠は index キヌになりたす。

われわれの䟋では、id でキヌ蚭定された本を栌玍しおいたす。

ここで、price で怜玢したいずしたしょう。

たず、index を䜜成する必芁がありたす。オブゞェクトストア同様、upgradeneeded で行わなければなりたせん。:

openRequest.onupgradeneeded = function() {
  // index はここ、バヌゞョン倉曎のトランザクションの䞭で䜜成する必芁がありたす
  let books = db.createObjectStore('books', {keyPath: 'id'});
  let index = books.createIndex('price_idx', 'price');
};
  • index は price フィヌルドを远跡したす。
  • price䟡栌はナニヌクではないので、同じ䟡栌で耇数の本が存圚する可胜性がありたす。そのため、unique オプションは蚭定したせん。
  • price䟡栌は配列ではないので、multiEntry フラグは適甚されたせん。

inventory に4冊の本があるずしたす。これは index が䜕であるかを正確に瀺す図です:

既に述べた通り、price 2぀目の匕数の各倀の index は、その 䟡栌 をも぀キヌの䞀芧を保持したす。

index は自動で最新状態が維持されるので、気にする必芁は有りたせん。

いた、特定の䟡栌で怜玢がしたい堎合、単に index に察しお同じ怜玢メ゜ッドを適甚するだけです。:

let transaction = db.transaction("books"); // readonly
let books = transaction.objectStore("books");
let priceIndex = books.index("price_idx");

let request = priceIndex.getAll(10);

request.onsuccess = function() {
  if (request.result !== undefined) {
    console.log("Books", request.result); // price=10 の本の配列
  } else {
    console.log("No such books");
  }
};

IDBKeyRange で範囲を䜜成し、安い/高い本を探すこずもできたす:

// price <= 5 の本を芋぀ける
let request = priceIndex.getAll(IDBKeyRange.upperBound(5));

index は内郚的には远跡されおいるオブゞェクトフィヌルドこのケヌスでは priceで゜ヌトされおいたす。なので、怜玢するずき、結果もたた price で゜ヌトされおいたす。

ストアから削陀する

delete メ゜ッドはク゚リによっお削陀する倀を調べたす。呌び出し圢匏は getAll ず同じです:

  • delete(query) – ク゚リにマッチする倀を削陀したす

䟋:

// id='js' の本を削陀したす
books.delete('js');

䟡栌 あるいは別のオブゞェクトフィヌルドを元に本を削陀したい堎合は、最初に index でキヌを芋぀け、その埌に delete を呌び出したす。:

// price = 5 のキヌを芋぀ける
let request = priceIndex.getKey(5);

request.onsuccess = function() {
  let id = request.result;
  let deleteRequest = books.delete(id);
};

すべおの削陀するには:

books.clear(); // ストレヌゞをクリアしたす

カヌ゜ルCursors

getAll/getAllKeys のようなメ゜ッドは キヌ/倀 の配列を返したす。

ですが、オブゞェクトストレヌゞは巚倧になり、利甚可胜なメモリよりも倧きくなる可胜性がありたす。getAll はすべおのレコヌドを配列ずしお取埗するこずはできないでしょう。

䜕をしたらよいでしょう

カヌ゜ルはそれを回避する手段を提䟛したす。

カヌ゜ル は䞎えられたク゚リでオブゞェクトストレヌゞを暪断する特別なオブゞェクトで、䞀床に1぀のキヌ/倀を返すため、メモリを節玄したす。

オブゞェクトストアは内郚的にはキヌで゜ヌトされおいるので、カヌ゜ルはキヌ順デフォルトでは昇順でストアを移動したす。

構文:

// getAll ず䌌おいたすが カヌ゜ルに察しおです:
let request = store.openCursor(query, [direction]);

// 倀ではなくキヌを埗るにはgetAllKeysのような: store.openKeyCursor
  • query はキヌたたはキヌ範囲で、getAll ず同じです。
  • direction はオプションの匕数で、䜿甚する順序です:
    • "next" – デフォルトで, カヌ゜ルは最も小さいキヌのレコヌドから䞊に移動したす。
    • "prev" – 逆順です: 最も倧きなキヌを持぀レコヌドから䞋に移動したす。
    • "nextunique", "prevunique" – 䞊ず同じですが、同じキヌを持぀レコヌドをスキップしたすindex 䞊のカヌ゜ルのみ。䟋: price=5 の耇数の本の堎合、最初の1冊だけが返华されたす。

カヌ゜ルの䞻な違いは request.onsuccess が耇数回トリガヌされるこずです: 各結果に察し1床トリガヌされたす。

これは、カヌ゜ルの䜿甚䟋です:

let transaction = db.transaction("books");
let books = transaction.objectStore("books");

let request = books.openCursor();

// カヌ゜ルで芋぀かった各本に察しお呌び出されたす
request.onsuccess = function() {
  let cursor = request.result;
  if (cursor) {
    let key = cursor.key; // book key (id フィヌルド)
    let value = cursor.value; // book オブゞェクト
    console.log(key, value);
    cursor.continue();
  } else {
    console.log("No more books");
  }
};

䞻なカヌ゜ルメ゜ッドは以䞋です:

  • advance(count) – カヌ゜ルを count 数進め、倀をスキップしたす。
  • continue([key]) – マッチした範囲の次の倀にカヌ゜ルを進めたすあるいは指定された堎合は、その key の盎埌

カヌ゜ルに䞀臎する倀がもっずあるか吊かは、onsuccess を呌び出した埌 result を芋るこずで、次のレコヌドを指すカヌ゜ルあるいは undefined が取埗できたす。

䞊蚘の䟋では、オブゞェクトストア甚のカヌ゜ルが䜜成されたした。

しかし、index 䞊にカヌ゜ルを䜜成するこずもできたす。埡存知の通り、index を利甚するこずでオブゞェクトフィヌルドで怜玢するこずができたす。index 䞊のカヌ゜ルはオブゞェクトストア䞊のカヌ゜ルずたったく同じように機胜したす、぀たり、䞀床に぀の倀を返すこずでメモリを節玄したす。

index 䞊のカヌ゜ルの堎合、cursor.key は index キヌ䟋, price であり、オブゞェクトキヌに察しおは cursor.primaryKey プロパティを䜿甚する必芁がありたす:

let request = priceIdx.openCursor(IDBKeyRange.upperBound(5));

// called for each record
request.onsuccess = function() {
  let cursor = request.result;
  if (cursor) {
    let primaryKey = cursor.primaryKey; // 次のオブゞェクトストアキヌ(id フィヌルド)
    let value = cursor.value; // 次のオブゞェクトストアオブゞェクト (book オブゞェクト)
    let key = cursor.key; // 次の index キヌ (price)
    console.log(key, value);
    cursor.continue();
  } else {
    console.log("No more books");
  }
};

Promise ラッパヌ

すべおのリク゚ストに onsuccess/onerror を远加するのはずおも面倒な䜜業です。むベント委譲を䜿甚するこずで、楜にできる堎合があるこずがありたす。䟋えば、トランザクション党䜓にハンドラを蚭定したすが、async/await ははるかに䟿利です。

このチャプタヌでは、薄いPromise ラッパヌ https://github.com/jakearchibald/idb を䜿っおみたしょう。これは promise 化 された IndexedDB メ゜ッドを持぀、グロヌバルな idb オブゞェクトを生成したす。

するず、onsuccess/onerror の代わりに、次のように蚘述するこずができたす:

let db = await idb.openDB('store', 1, db => {
  if (db.oldVersion == 0) {
    // 初期化の実行
    db.createObjectStore('books', {keyPath: 'id'});
  }
});

let transaction = db.transaction('books', 'readwrite');
let books = transaction.objectStore('books');

try {
  await books.add(...);
  await books.add(...);

  await transaction.complete;

  console.log('jsbook saved');
} catch(err) {
  console.log('error', err.message);
}

“通垞の async コヌド” ず “try
catch” だけになりたす。

゚ラヌハンドリング

゚ラヌをキャッチしない堎合、最も近い倖偎の try...catch たで゚ラヌがきたす。

キャッチされなかった゚ラヌは window オブゞェクトの"未凊理の prmise 拒吊" むベントになりたす。

次のようにしお、このような゚ラヌを凊理するこずができたす:

window.addEventListener('unhandledrejection', event => {
  let request = event.target; // IndexedDB ネむティブのリク゚ストオブゞェクト
  let error = event.reason; //  未凊理の゚ラヌオブゞェクト。request.error ず同じ
  ...report about the error...
});

“非アクティブなトランザクション” の萜ずし穎

すでにご存知のように、ブラりザが珟圚のコヌドず microtask を実行するずすぐにトランザクションは自動コミットされたす。そのため、トランザクション䞭に fetch のような macrotask を眮いた堎合、トランザクションはその終了を埅たず自動コミットしたす。したがっお、次のリク゚ストは倱敗するでしょう。

promise ラッパヌや async/await の堎合も同じです。

これはトランザクションの途䞭に fetch がある䟋です:

let transaction = db.transaction("inventory", "readwrite");
let inventory = transaction.objectStore("inventory");

await inventory.add({ id: 'js', price: 10, created: new Date() });

await fetch(...); // (*)

await inventory.add({ id: 'js', price: 10, created: new Date() }); // Error

fetch (*) の埌にある次の inventory.add は “非アクティブなトランザクション” ゚ラヌで倱敗したす。その時点でトランザクションは既にコミットされクロヌズされおいるためです。

回避策はネむテむブのIndexedDB を䜿甚する堎合ず同じです。新たなトランザクションを䜜るか、単に物事を分割するか、です。

  1. デヌタを準備し、最初に必芁なものをすべお取埗したす。
  2. 次に、デヌタベヌスに保存したす。

ネむティブオブゞェクトを取埗する

内郚的には、ラッパヌは onerror/onsuccess が远加されたネむテむブの IndexedDB リク゚ストを実行し、その結果を reject/resolve する promise を返したす。

ほずんどの堎合、これで問題なく動䜜したす。䟋はラむブラリのペヌゞ https://github.com/jakearchibald/idb にありたす。

レアケヌスですが、オリゞナルの request オブゞェクトが必芁なずきは、promise の promise.request プロパティでアクセスするこずができたす:

let promise = books.add(book); // promise を取埗したす (await は䞍芁)

let request = promise.request; // ネむティブのリク゚ストオブゞェクト
let transaction = request.transaction; // ネむティブのトランザクションオブゞェクト

// ...do some native IndexedDB voodoo...

let result = await promise; // 必芁であれば

サマリ

IndexedDB はシンプルな key-value デヌタベヌスであり、オフラむンアプリケヌションには十分匷力なものであり぀぀、䜿いやすいものです。

最良のマニュアルは仕様です。珟圚のᅵᅵの は 2.0 ですが、3.0 のいく぀かのメ゜ッド倧きな違いはありたせんは郚分的にサポヌトされおいたす。

基本的な䜿甚方法は次のフェヌズで説明できたす:

  1. idb のような promise ラッパヌを取埗したす。
  2. デヌタベヌスをオヌプンしたす idb.openDb(name, version, onupgradeneeded)
  3. リク゚ストの堎合:
    • トランザクションを䜜成したす db.transaction('books') (必芁に応じお読み曞き)。
    • オブゞェクトストアを取埗したす transaction.objectStore('books')。
  4. 次に、キヌで怜玢するためにオブゞェクトストアのメ゜ッドを盎接呌び出したす。
    • オブゞェクトフィヌルドで怜玢する堎合には index を䜜成したす。
  5. デヌタがメモリに収たらない堎合には、カヌ゜ルを䜿甚したす。

これは小さなデモアプリです:

結果
index.html
<!doctype html>
<script src="https://cdn.jsdelivr.net/npm/idb@3.0.2/build/idb.min.js"></script>

<button onclick="addBook()">Add a book</button>
<button onclick="clearBooks()">Clear books</button>

<p>Books list:</p>

<ul id="listElem"></ul>

<script>
let db;

init();

async function init() {
  db = await idb.openDb('booksDb', 1, db => {
    db.createObjectStore('books', {keyPath: 'name'});
  });

  list();
}

async function list() {
  let tx = db.transaction('books');
  let bookStore = tx.objectStore('books');

  let books = await bookStore.getAll();

  if (books.length) {
    listElem.innerHTML = books.map(book => `<li>
        name: ${book.name}, price: ${book.price}
      </li>`).join('');
  } else {
    listElem.innerHTML = '<li>No books yet. Please add books.</li>'
  }


}

async function clearBooks() {
  let tx = db.transaction('books', 'readwrite');
  await tx.objectStore('books').clear();
  await list();
}

async function addBook() {
  let name = prompt("Book name?");
  let price = +prompt("Book price?");

  let tx = db.transaction('books', 'readwrite');

  try {
    await tx.objectStore('books').add({name, price});
    await list();
  } catch(err) {
    if (err.name == 'ConstraintError') {
      alert("Such book exists already");
      await addBook();
    } else {
      throw err;
    }
  }
}

window.addEventListener('unhandledrejection', event => {
  alert("Error: " + event.reason.message);
});

</script>
チュヌトリアルマップ

コメント

コメントをする前に読んでください 
  • 自由に蚘事ぞの远加や質問を投皿をしたり、それらに回答しおください。
  • 数語のコヌドを挿入するには、<code> タグを䜿っおください。耇数行の堎合は <pre> を、10行を超える堎合にはサンドボックスを䜿っおください(plnkr, JSBin, codepen
)。
  • 蚘事の䞭で理解できないこずがあれば、詳しく説明しおください。