IndexedDB

【Dexie.js】トランザクション内で非同期処理してみる

Dexie.tranasaction()とは

本関数を使えば、IndexedDBでもトランザクションを制御することができる。

ただし、transaction内で非同期APIをawaitとかで同期的に呼び出したりしても待ってくれず自動でコミットされてしまう。

The Auto-Commit Behavior of IndexedDB Transactions

IndexedDB will commit a transaction as soon as it isn’t used within a tick. This means that you MUST NOT call any other async API (at least not wait for it to finish) within a transaction scope. If you do, you will get a TransactionInactiveError thrown at you. To avoid this, you may use Dexie.waitFor(), but use it with caution.

Dexie.transaction()
The easiest way to use IndexedDB. A lightweight, minimalistic wrapper that provides a straightforward API for developers...

この問題については、Dexie.waitFor() を使用することで避けれるようなので、試してみる。

Dexie.waiFor()とは

IndexedDBトランザクション内で、非同期APIを呼び出しても処理を待ちトランザクションを存続できる。

つまり、waitForで指定されたPromiseが解決されるまで、トランザクションを存続し、自動コミットされるのを防いでくれる。

注意点として、指定されたPromiseを待機してる間、CPUに負荷がかかる可能性があるとのこと。

サンプルコード

共通処理

今回は存在しないAPIに対してリクエストを投げて動きを確認している

// 404:NotFound
const NG_REQUEST = "https://qiita.com/api/v2/test";

function createDatabase() {
  try {
    const databaseName = "TransactonTestDatabase";
    Dexie.delete(databaseName);
    const db = new Dexie(databaseName);

    db.version(1).stores({
      friends: `
        id,
        name,
        age`,
    });
    return db;
  } catch (error) {
    throw error;
  }
}

NG

async function ngTransaction() {
  const db = createDatabase();

  try {
    await db.transaction("rw", db.friends, async () => {
      await db.friends.put({ id: 1, name: "Josephine", age: 21 });
      await db.friends.put({ id: 2, name: "Per", age: 75 });

      // 非同期処理
      const res = await fetch(NG_REQUEST);

      if (res.status != 200) {
        throw "ロールバック実行";
      }
    });
  } catch (error) {
    console.error("An error occurred:", error);
  }
}

一見、非同期処理をawaitで待っているので、うまく動きそうだがこれだとエラーをthrowしてもfriendsテーブルへのオブジェクト追加はロールバックされずに、先に自動でコミットされてしまう

OK

async function okTransaction() {
  const db = createDatabase();

  try {
    await db.transaction("rw", db.friends, async () => {
      await db.friends.put({ id: 1, name: "Josephine", age: 21 });
      await db.friends.put({ id: 2, name: "Per", age: 75 });

      // 非同期処理
      // ★Dexie.waitFor
      const res = await Dexie.waitFor(fetch(NG_REQUEST ));

      if (res.status != 200) {
        throw "ロールバック実行";
      }
    });
  } catch (error) {
    console.error("An error occurred:", error);
  }
}

Dexie.waitFor関数を使うと、非同期処理の結果を待ち、エラーが発生するとロールバックされていることを確認できた

参考