В эти выходные я имел удовольствие разбираться с асинхронными api IndexedDB.
Поле разбора примера от mozilla мне показалось, что код не так хорош, как мог бы быть.
И я решил для развития своих псионных навыков работы c IndexedDB написать обертку к его асинхронному api.
Если честно, то я не любитель писать статьи, потому что мне всегда было лень читать слова, я люблю читать код, поэтому в моих статьях его как правило много ))).
Если вы тоже любите код, то я выложил его на github
Фактически эта статья описывает то что написано в примерах использования обертки, ну может чуть больше )
Кстати про код, многие наверно подумали, вот посмотрел бы ты на код который я сейчас или недавно читал, понаписали тут…
Да, иногда подсовывают ещё ту фигню. Я отношусь к такому коду философски и с юмором. Искренне надеюсь, что не насмешу вас, любезный читатель )
Что мне не понравилось в текущем api
В том, что предлагают сейчас нам разработчики стандарта html5/браузеров есть несколько недостатков:
- Приходиться писать лишний код для реализации цепочек вызовов;
- Писать свои обработчики событий на логические операции: создание хранилища и его наполнения, обновление/создание сущности, завершение работы с курсором;
- Поддерживать несколько браузеров. Сейчас многие браузеры не реализовали те или иные возможности IndexedDB, например count и onupgradeneeded нет в webkit;
- Избыточность — помнить о многих функциях делающих примерно одно и то же. Например: IDBObjectStore.get, IDBObjectStore.openCursor, IDBIndex.openKeyCursor, IDBIndex.openCursor
Возьмем, например создание хранилища. Мне лично кажется, что такой код громоздок. Мне бы больше понравилось, если бы код выглядел так:
- var init = function(event) {
- if(db.containts('customers'))
- return;
- db.createStore('customers', { keyPath: 'ssn' })
- .createIndex('name', { unique: false })
- .createIndex('age', { unique: false })
- .createIndex('email', { unique: true })
- .complete(function(event) {
- console.info('store customers created', event);
- })
- .add(customerData)
- .error(errorHandler)
- .success(function(event) {
- console.info('add success', event);
- });
- };
Этот код чуть более функционален, чем код из примера, например, на все операции зарегистрированы события, такие как:
- есть дополнительная обработка завершения создания и наполнения — complete
- обработка ошибок
- успешное добавление
Так же, т.к. работа ведется не в приложении, которое перед этим было установлено клиенту, а с web. А значит, страница могла быть загружена впервые, я бы хотел добавить некоторые события в контекст работы с базой данных, например:
- var db = new inDB({ name: 'testDatabase', version: 42 })
- .error(errorHandler)
- // before ready
- .init(init)
- .versionChange( function(event) {
- console.log('version changed, timeStamp:', event.timeStamp, ', new version:', this.version);
- })
- // WebKit, as of 2012-02-22, does not yet implement this.
- .upgradeNeeded(function(event) {
- console.log('onupgradeneeded, newVersion:', event.newVersion, ', oldVersion:', event.oldVersion, ', timeStamp:', event.timeStamp, event);
- init.call(this, event);
- })
- .ready(function(event) {
- //...
- });
Т.е. узнать об изменении версии, инициализировать хранилища и заполнить их данными, а потом уже начать работу, когда хранилище будет готово, аснихронка, едрён компот.
Если я вас не убедил, то далее читать бессмыслено )
Мой путь
Что касается меня, то этих соображений для меня было достаточно, чтоб продолжить. Потому что то, что я увидел далее в примере mozilla, мне не понравилось ещё больше. Т.е. большое спасибо ребятам за пример, но я так жить не хочу ). Мне бы гораздо более понравилось, если бы это выглядело так:
- db.openStore('customers')
- .get('444-44-4444')
- .error(errorHandler)
- .success(function(event) {
- console.log(this.result);
- });
А для выбора по другому индексу так:
- db.openStore('customers')
- .get('name', 'Artur')
- //...
Для выбора данных из курсора я хочу иметь контекст выполнения и отдельные блоки для начала операции, промежуточных значений и конца, как-то так:
- db.openStore('customers')
- .get(function(query) {
- return query
- .bound('age', 30, 60, true, true); // all age 30 > x && < 60
- // only one predicate by design indexeddb, please use where after get
- })
- .where(function(item){
- return item.email.substr(-8).toLowerCase() != 'home.org';
- })
- .error(errorHandler)
- .start(function(context) {
- context.result = [];
- console.time('get all by IDBKeyRange');
- })
- .ended(function(context) {
- console.timeEnd('get all by IDBKeyRange', context.result);
- })
- .success(function(event, context) {
- var customer = this.result.value;
- console.info('getted by IDBKeyRange', customer.ssn, customer, event, this);
- if(customer.ssn == '111-11-1111') {
- customer.age = 6;
- // only for cursor
- context.update(customer);
- }
- context.result.push(customer);
- });
Свое видение о том, как должен был бы выглядеть асинхронный доступ к базе данных с такой архитектурой, я реализовал в indb.js.
Там ещё много вкусного, например, можно регистрировать события об изменении/создании объектов хранилища, на порядок проще удалять базу или хранилище.
PS: Для тех кто запустит код примера в FF, сейчас там есть любопытный баг в реализации движка IndexedDB. Если поменять customer.age = 6; на ++customer.age; то после пересоздания базы вы получите возраст Maria 60 лет, а не 41. Любопытно так же, что если перезапустить скрипт до отрабатываемой очистки по timeout в 5 секунд, то все будет Ok.
Автор: Sigura