Тестирование поведения при ошибках хранилища

idb-keyval — это лёгкая обёртка над IndexedDB, предназначенная для удобного хранения ключ-значение в браузере. Несмотря на простоту API, важно понимать, что работа с IndexedDB всегда сопряжена с возможными ошибками: переполнение квоты, закрытие базы данных браузером, блокировка транзакций, ошибки сериализации объектов и др. Библиотека предоставляет стандартные методы (get, set, del, clear, keys), каждый из которых возвращает Promise, что облегчает работу с асинхронной обработкой ошибок через .catch() или try…catch с async/await.


Обработка ошибок при записи данных

Метод set(key, value) используется для записи данных в IndexedDB. Возможные источники ошибок:

  • Превышение квоты хранилища: браузер ограничивает объём локальных данных.
  • Неподдерживаемые типы данных: IndexedDB поддерживает объекты, строки, числа, массивы и Blob; функции и циклические структуры вызовут ошибку сериализации.
  • Закрытая база данных: если транзакция была прервана или база не открыта.

Пример с обработкой ошибок:

import { set } from 'idb-keyval';

async function saveData(key, value) {
  try {
    await set(key, value);
    console.log('Данные успешно сохранены');
  } catch (error) {
    console.error('Ошибка при записи данных:', error);
  }
}

Важный момент: error может быть объектом DOMException, содержащим код ошибки (QuotaExceededError, AbortError и др.), что позволяет точечно реагировать на разные типы проблем.


Чтение данных с учётом возможных ошибок

Метод get(key) возвращает значение по ключу. Ошибки чтения могут возникать при:

  • Закрытии транзакции до её завершения.
  • Коррупции данных, хотя это крайне редкий случай.
  • Несуществующем ключе — библиотека возвращает undefined, что не считается ошибкой.

Пример обработки чтения:

import { get } from 'idb-keyval';

async function loadData(key) {
  try {
    const value = await get(key);
    if (value === undefined) {
      console.warn('Данные по ключу не найдены');
    } else {
      console.log('Значение:', value);
    }
  } catch (error) {
    console.error('Ошибка при чтении данных:', error);
  }
}

Удаление и очистка хранилища

Методы del(key) и clear() также возвращают промисы. Возможные ошибки:

  • Ошибка транзакции (AbortError) — если операция не может быть выполнена из-за конфликта с другими транзакциями.
  • Недоступность базы — при некорректном закрытии IndexedDB.

Пример с точной обработкой:

import { del, clear } from 'idb-keyval';

async function removeData(key) {
  try {
    await del(key);
    console.log('Ключ удалён');
  } catch (error) {
    console.error('Ошибка при удалении ключа:', error);
  }
}

async function clearStore() {
  try {
    await clear();
    console.log('Хранилище очищено');
  } catch (error) {
    console.error('Ошибка при очистке хранилища:', error);
  }
}

Тестирование поведения при ошибках

Для надёжных приложений необходимо тестировать сценарии ошибок:

  1. Искусственное переполнение хранилища:
try {
  const largeData = new Array(1e8).fill('x');
  await set('big', largeData);
} catch (error) {
  console.log(error.name); // QuotaExceededError
}
  1. Попытка записи неподдерживаемого типа:
try {
  await set('func', () => {});
} catch (error) {
  console.log(error.message); // DataCloneError
}
  1. Закрытая или заблокированная база данных: можно тестировать, создавая две параллельные транзакции и вызывая abort() одной из них.

Логирование и пользовательские уведомления

Реальные приложения должны:

  • Логировать ошибки в консоль или систему мониторинга.
  • Предоставлять пользователю понятное уведомление, если данные не сохранены.
  • Применять стратегии повторной записи: повтор через таймер или откладывание операции до следующего запуска приложения.

Пример с пользовательской реакцией:

async function saveWithRetry(key, value) {
  try {
    await set(key, value);
  } catch (error) {
    console.error('Ошибка при сохранении:', error);
    // Повторная попытка через 2 секунды
    setTimeout(() => saveWithRetry(key, value), 2000);
  }
}

Проверка состояния хранилища перед записью

Чтобы минимизировать ошибки, полезно проверять доступное пространство и тип данных заранее:

function isSerializable(value) {
  try {
    structuredClone(value);
    return true;
  } catch {
    return false;
  }
}

Использование structuredClone позволяет определить, можно ли объект безопасно сохранить в IndexedDB, не дожидаясь исключения при записи.