Система обработки ошибок в Dexie.js построена вокруг специализированной иерархии исключений, которая позволяет точно определять причины сбоев при работе с IndexedDB. В отличие от прямого использования IndexedDB API, где разработчик часто сталкивается с малоинформативными объектами ошибок браузера, Dexie предоставляет собственные классы исключений с более понятными названиями и предсказуемым поведением.
Большинство ошибок наследуются от базового класса
Dexie.DexieError, который в свою очередь наследует
стандартный объект Error.
Пример общей обработки ошибок:
try {
await db.users.add({
id: 1,
name: "Alex"
});
}
catch (error) {
console.error(error.name);
console.error(error.message);
}
Свойство name обычно содержит конкретный тип ошибки:
ConstraintError
DataError
TransactionInactiveError
VersionError
Такой подход позволяет строить избирательную обработку различных исключительных ситуаций.
Практически все специфические ошибки библиотеки наследуются от:
Dexie.DexieError
Структура объекта:
try {
await db.users.add(user);
}
catch (error) {
console.log(error.name);
console.log(error.message);
console.log(error.stack);
}
Основные свойства:
| Свойство | Назначение |
|---|---|
| name | Имя ошибки |
| message | Описание причины |
| stack | Стек вызовов |
| inner | Вложенная ошибка IndexedDB |
Поле inner особенно полезно для глубокого анализа
проблем браузерного движка.
Одна из самых распространённых ошибок.
Возникает при нарушении ограничения уникальности ключей или индексов.
Пример:
db.version(1).stores({
users: "id,&email"
});
Уникальный индекс:
&email
Попытка добавить дубликат:
await db.users.add({
id: 1,
email: "admin@mail.com"
});
await db.users.add({
id: 2,
email: "admin@mail.com"
});
Результат:
ConstraintError
Обработка:
try {
await db.users.add(user);
}
catch (error) {
if (error.name === "ConstraintError") {
console.log("Запись уже существует");
}
}
Появляется при передаче недопустимых данных в IndexedDB.
Пример:
await db.users.get(NaN);
Или:
await db.users.where("id").equals(undefined).toArray();
Возможный результат:
DataError
undefined как ключа;NaN;Некорректно:
await db.users.delete(undefined);
Корректно:
if (id !== undefined) {
await db.users.delete(id);
}
Возникает при передаче недопустимых аргументов в методы Dexie.
Пример:
db.table(null);
Или:
db.transaction("rw");
Если параметры метода не соответствуют ожидаемому формату, Dexie может сгенерировать:
InvalidArgumentError
Сообщает о попытке обращения к таблице, которой нет в схеме базы данных.
Пример:
db.table("orders");
Если таблица отсутствует:
db.version(1).stores({
users: "++id,name"
});
возникнет:
InvalidTableError
Безопасный вариант:
if (db.tables.some(t => t.name === "orders")) {
const table = db.table("orders");
}
Возникает при попытке открыть или удалить несуществующую базу данных.
Пример:
await Dexie.delete("UnknownDatabase");
В зависимости от ситуации может появиться:
NoSuchDatabaseError
Подобные ошибки чаще встречаются в административных сценариях управления несколькими базами.
Сообщает о невозможности открыть базу данных.
Пример:
const db = new Dexie("AppDB");
await db.open();
Причинами могут быть:
Результат:
OpenFailedError
Обработка:
try {
await db.open();
}
catch (error) {
if (error.name === "OpenFailedError") {
console.error("Не удалось открыть базу");
}
}
Связана с проблемами версионирования базы данных.
Пример:
db.version(2).stores({
users: "++id,name"
});
Если браузер обнаруживает конфликт между версиями:
VersionError
Чаще всего подобные ошибки возникают при:
Возникает во время обновления структуры базы данных.
Пример миграции:
db.version(2)
.stores({
users: "++id,name,age"
})
.upgrade(tx => {
return tx.table("users")
.toCollection()
.modify(user => {
user.age = 18;
});
});
Если внутри функции обновления возникает исключение:
throw new Error("Migration failed");
Dexie оборачивает его в:
UpgradeError
Обычно содержит ссылку на внутреннюю причину:
catch(error) {
console.log(error.inner);
}
Появляется при обращении к закрытой базе данных.
Пример:
db.close();
await db.users.toArray();
Результат:
DatabaseClosedError
Безопасный подход:
if (!db.isOpen()) {
await db.open();
}
Возникает при использовании завершённой транзакции.
Пример:
await db.transaction("rw", db.users, async () => {
setTimeout(async () => {
await db.users.add({
name: "John"
});
}, 1000);
});
После завершения транзакции её контекст исчезает.
Попытка работы внутри асинхронного обработчика приводит к:
TransactionInactiveError
Использование:
setTimeout()
или сторонних асинхронных API внутри транзакции.
Появляется после принудительного прерывания транзакции.
Пример:
await db.transaction("rw", db.users, async () => {
Dexie.currentTransaction.abort();
});
Результат:
AbortError
Подобное поведение считается нормальным и часто используется для отката операций.
Используется в механизмах, связанных с ожиданием асинхронных операций.
Возникает при превышении допустимого времени ожидания.
Пример логики:
await Promise.race([
operation(),
timeoutPromise()
]);
При срабатывании тайм-аута может быть выброшен:
TimeoutError
Специальная ошибка пакетных операций.
Методы:
bulkAdd()
bulkPut()
bulkDelete()
могут завершиться частично успешно.
Пример:
await db.users.bulkAdd([
{id: 1},
{id: 2},
{id: 1}
]);
Результат:
BulkError
Содержит информацию о проблемных элементах.
Пример:
catch(error) {
console.log(error.failures);
console.log(error.failuresByPos);
}
Структура позволяет определить, какие записи были добавлены успешно, а какие вызвали ошибку.
Связана с массовым изменением записей.
Пример:
await db.users
.toCollection()
.modify(user => {
if (!user.email) {
throw new Error("Email required");
}
user.active = true;
});
Результат:
ModifyError
Ошибка содержит сведения о количестве успешных и неуспешных изменений.
Например:
catch(error) {
console.log(error.failedKeys);
console.log(error.successCount);
}
Появляется при преждевременном завершении транзакции.
Обычно связана со смешиванием различных асинхронных механизмов.
Пример потенциально проблемного кода:
await db.transaction("rw", db.users, async () => {
fetch("/api/users")
.then(response => response.json())
.then(data => {
return db.users.add(data);
});
});
Транзакция может завершиться раньше сетевого запроса.
В результате возможно появление:
PrematureCommitError
Сообщает об отсутствии IndexedDB в среде выполнения.
Встречается в:
Пример:
MissingAPIError
Для серверных приложений обычно используются специальные реализации IndexedDB либо мок-объекты.
На практике редко ограничиваются одной проверкой.
Пример:
try {
await db.users.add(user);
}
catch(error) {
switch (error.name) {
case "ConstraintError":
console.log("Дубликат записи");
break;
case "DataError":
console.log("Некорректные данные");
break;
case "DatabaseClosedError":
console.log("База закрыта");
break;
default:
console.error(error);
}
}
Такой подход обеспечивает понятную реакцию приложения на различные сценарии отказа.
Dexie поддерживает фильтрацию исключений по типу.
Пример:
db.users
.add(user)
.catch("ConstraintError", error => {
console.log("Пользователь уже существует");
});
Несколько типов:
.catch(error => {
console.error(error);
});
Подобный механизм делает код компактнее по сравнению с традиционными
проверками через if и switch.
При разработке крупных приложений ошибки Dexie обычно разделяют на несколько категорий:
Ошибки данных
Ошибки схемы
Ошибки жизненного цикла базы
Ошибки транзакций
Ошибки окружения
Подобная классификация позволяет централизованно строить систему логирования, мониторинга и восстановления после сбоев, сохраняя предсказуемое поведение приложения даже при возникновении сложных исключительных ситуаций во время работы с IndexedDB через Dexie.js.