JavaScript. Подробное руководство, 6-е издание, стр. 398

Помимо возможности извлекать объекты из хранилища по значению первичного ключа существует также возможность выполнить поиск по значениям других свойств объекта. Чтобы обеспечить эту возможность, в хранилище объектов можно определить любое количество индексов. (Способность индексировать объекты подчеркивается самим названием «IndexedDB».) Каждый индекс определяет вторичный ключ хранимых объектов. Эти индексы в целом могут быть неуникальными, и одному и тому же ключу может соответствовать множество объектов. Поэтому в операциях обращения к хранилищу объектов с использованием индекса обычно используется курсор, определяющий прикладной интерфейс для извлечения объектов из потока результатов по одному. Курсоры могут также использоваться для обращения к хранилищу объектов с использованием диапазона ключей (или индексов), и прикладной интерфейс IndexedDB включает объект, используемый для описания диапазонов (с верхней и/или с нижней границей, включающих или не включающих границы) ключей.

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

Концепция прикладного интерфейса IndexedDB чрезвычайно проста. Чтобы прочитать или изменить данные, сначала необходимо открыть требуемую базу данных (указав ее имя). Затем создать объект транзакции и с помощью этого объекта отыскать требуемое хранилище объектов в базе данных, также по имени. Наконец, отыскать объект вызовом метода

<b>get()</b>
хранилища объектов или сохранить новый объект вызовом метода
<b>put().</b>
(Или вызвать метод
<b>add(),</b>
если необходимо избежать затирания существующих объектов.) Если потребуется отыскать объекты по диапазону ключей, нужно создать объект
<b>IDBRange</b>
и передать его методу
<b>openCursor()</b>
хранилища объектов. Или, если потребуется выполнить запрос по вторичному ключу, отыскать именованный индекс в хранилище объектов и затем вызвать метод
<b>get()</b>
или
<b>openCursor()</b>
объекта-индекса.

Однако эта концептуальная простота осложняется тем фактом, что прикладной интерфейс должен быть асинхронным, чтобы веб-приложения могли пользоваться им, не блокируя основной поток выполнения броузера, управляющий пользовательским интерфейсом. (Спецификация IndexedDB определяет синхронную версию прикладного интерфейса для использования в фоновых потоках выполнения, но на момент написания этих строк ни один броузер еще не реализовал эту версию, поэтому она не рассматривается здесь.) Создание транзакции, а также поиск хранилища объектов и индексов являются простыми синхронными операциями. Но открытие базы данных, обновление хранилища объектов с помощью метода

<b>put()</b>
и получение хранилища или индекса с помощью метода
<b>get()</b>
или
<b>openCursor()</b>
являются асинхронными операциями. Все эти асинхронные методы немедленно возвращают объект запроса. В случае успешного или неудачного выполнения запроса броузер генерирует событие «success» или «error» в объекте запроса, которые можно обработать, определив обработчики событий с помощью свойств
<b>onsuccess</b>
и
<b>onerror</b>
. В обработчике
<b>onsuccess</b>
результат операции доступен в виде свойства
<b>result</b>
объекта запроса.

Одно из удобств этого асинхронного прикладного интерфейса заключается в простоте управления транзакциями. При типичном использовании прикладного интерфейса IndexedDB сначала открывается база данных. Это асинхронная операция, поэтому по ее выполнении вызывается обработчик

<b>onsuccess</b>
. В этом обработчике создается объект транзакции, и затем этот объект используется для поиска хранилища или хранилищ объектов, которые предполагается использовать. После этого производится серия вызовов методов
<b>get()</b>
и
<b>put()</b>
хранилищ объектов. Они также действуют асинхронно, поэтому непосредственно при их вызове ничего не происходит, но запросы, сгенерированные этими методами
<b>get()</b>
и
<b>put(),</b>
автоматически будут связаны с объектом транзакции. При необходимости можно отменить все операции в транзакции, ожидающие выполнения, и откатить любые уже выполненные операции вызовом метода
<b>abort()</b>
объекта транзакции. Во многих других прикладных интерфейсах к базам данных объект транзакции обычно имеет метод
<b>commit(),</b>
подтверждающий транзакцию. Однако в IndexedDB транзакция подтверждается после выхода из обработчика
<b>onsuccess</b>
, создавшего транзакцию, когда броузер вернется в цикл обработки событий, и после выполнения всех операций, запрошенных в транзакции (без запуска новых операций в их функциях обратного вызова). Такая схема, на первый взгляд, кажется слишком сложной, но в практическом применении она очень проста. При использовании прикладного интерфейса IndexedDB программист вынужден создавать объекты транзакций, чтобы получить доступ к хранилищам объектов, но в обычных ситуациях ему даже не приходится задумываться о транзакциях.

Наконец, существует один особый вид транзакций, обеспечивающий возможность работы очень важной части прикладного интерфейса IndexedDB. Создать новую базу данных с использованием интерфейса IndexedDB API очень просто: достаточно выбрать имя и запросить открытие этой базы данных. Но новая база данных создается абсолютно пустой, и она совершенно бесполезна, пока в нее не будет добавлено одно или более хранилищ объектов (и, возможно, нескольких индексов). Создавать хранилища объектов и индексы можно только внутри обработчика события

<b>onsuccess</b>
объекта запроса, возвращаемого методом
<b>setVersion()</b>
объекта базы данных. Метод
<b>setVersion()</b>
позволяет указать номер версии базы данных - в обычной ситуации номер версии должен изменяться при каждом изменении структуры базы данных. Однако более важно, что метод
<b>setVersion()</b>
неявно запускает специальную транзакцию, позволяющую вызвать метод
<b>сreateObjectStore()</b>
объекта базы данных и метод
<b>createlndex()</b>
хранилища объектов.

Теперь, получив представление о прикладном интерфейсе IndexedDB, вы сможете самостоятельно разобраться в примере 22.15. Этот пример использует IndexedDB для создания базы данных, отображающей почтовые индексы США в названия городов, и выполнения запросов к ней. Он демонстрирует многие, хотя и не все, основные особенности IndexedDB. На момент написания этих строк пример действовал в Firefox 4 и Chrome 11, но из-за того, что спецификация все еще продолжала меняться и реализации находились на предварительной стадии разработки, велика вероятность, что он не будет работать именно так, как описывается здесь, когда вы будете читать эти строки. Однако общая структура примера должна сохранить свою полезность для вас. Пример 22.15 получился достаточно длинным, но в нем имеется большое количество комментариев, которые облегчат его изучение.