JavaScript / Обертка для api асинхронного IndexedDB

в 20:45, , рубрики: html5, indexeddb, javascript, метки: , ,

В эти выходные я имел удовольствие разбираться с асинхронными api IndexedDB.

Поле разбора примера от mozilla мне показалось, что код не так хорош, как мог бы быть.

И я решил для развития своих псионных навыков работы c IndexedDB написать обертку к его асинхронному api.

Если честно, то я не любитель писать статьи, потому что мне всегда было лень читать слова, я люблю читать код, поэтому в моих статьях его как правило много ))).

Если вы тоже любите код, то я выложил его на github


Фактически эта статья описывает то что написано в примерах использования обертки, ну может чуть больше )

Кстати про код, многие наверно подумали, вот посмотрел бы ты на код который я сейчас или недавно читал, понаписали тут…

Да, иногда подсовывают ещё ту фигню. Я отношусь к такому коду философски и с юмором. Искренне надеюсь, что не насмешу вас, любезный читатель )

Что мне не понравилось в текущем api

В том, что предлагают сейчас нам разработчики стандарта html5/браузеров есть несколько недостатков:

  • Приходиться писать лишний код для реализации цепочек вызовов;
  • Писать свои обработчики событий на логические операции: создание хранилища и его наполнения, обновление/создание сущности, завершение работы с курсором;
  • Поддерживать несколько браузеров. Сейчас многие браузеры не реализовали те или иные возможности IndexedDB, например count и onupgradeneeded нет в webkit;
  • Избыточность — помнить о многих функциях делающих примерно одно и то же. Например: IDBObjectStore.get, IDBObjectStore.openCursor, IDBIndex.openKeyCursor, IDBIndex.openCursor

Возьмем, например создание хранилища. Мне лично кажется, что такой код громоздок. Мне бы больше понравилось, если бы код выглядел так:

  1. var init = function(event) {
  2.  
  3.     if(db.containts('customers'))
  4.         return;
  5.  
  6.     db.createStore('customers', { keyPath: 'ssn' })
  7.         .createIndex('name', { unique: false })
  8.         .createIndex('age', { unique: false })
  9.         .createIndex('email', { unique: true })
  10.         .complete(function(event) {
  11.             console.info('store customers created', event);
  12.         })
  13.         .add(customerData)
  14.         .error(errorHandler)
  15.         .success(function(event) {
  16.             console.info('add success', event);
  17.         });
  18.  
  19. };

Этот код чуть более функционален, чем код из примера, например, на все операции зарегистрированы события, такие как:

  • есть дополнительная обработка завершения создания и наполнения — complete
  • обработка ошибок
  • успешное добавление

Так же, т.к. работа ведется не в приложении, которое перед этим было установлено клиенту, а с web. А значит, страница могла быть загружена впервые, я бы хотел добавить некоторые события в контекст работы с базой данных, например:

  1. var db = new inDB({ name: 'testDatabase', version: 42 })
  2. .error(errorHandler)
  3. // before ready
  4. .init(init)
  5. .versionChange( function(event) {
  6.     console.log('version changed, timeStamp:', event.timeStamp, ', new version:', this.version);
  7. })
  8. // WebKit, as of 2012-02-22, does not yet implement this. 
  9. .upgradeNeeded(function(event) {
  10.  
  11.     console.log('onupgradeneeded, newVersion:', event.newVersion, ', oldVersion:', event.oldVersion, ', timeStamp:', event.timeStamp, event);
  12.  
  13.     init.call(this, event);
  14. })
  15. .ready(function(event) {
  16.     //...
  17. });

Т.е. узнать об изменении версии, инициализировать хранилища и заполнить их данными, а потом уже начать работу, когда хранилище будет готово, аснихронка, едрён компот.
Если я вас не убедил, то далее читать бессмыслено )

Мой путь

Что касается меня, то этих соображений для меня было достаточно, чтоб продолжить. Потому что то, что я увидел далее в примере mozilla, мне не понравилось ещё больше. Т.е. большое спасибо ребятам за пример, но я так жить не хочу ). Мне бы гораздо более понравилось, если бы это выглядело так:

  1. db.openStore('customers')
  2.     .get('444-44-4444')
  3.     .error(errorHandler)
  4.     .success(function(event) {
  5.         console.log(this.result);
  6.     });

А для выбора по другому индексу так:

  1. db.openStore('customers')
  2.     .get('name', 'Artur')
  3.     //...

Для выбора данных из курсора я хочу иметь контекст выполнения и отдельные блоки для начала операции, промежуточных значений и конца, как-то так:

  1. db.openStore('customers')
  2.     .get(function(query) {
  3.          return query
  4.                 .bound('age', 30, 60, true, true); // all age 30 > x && < 60
  5.                 // only one predicate by design indexeddb, please use where after get
  6.     })
  7.     .where(function(item){
  8.         return item.email.substr(-8).toLowerCase() != 'home.org';
  9.     })
  10.     .error(errorHandler)
  11.     .start(function(context) {
  12.         context.result = [];
  13.         console.time('get all by IDBKeyRange');
  14.     })
  15.     .ended(function(context) {
  16.         console.timeEnd('get all by IDBKeyRange', context.result);
  17.     })
  18.     .success(function(event, context) {
  19.         var customer = this.result.value;
  20.  
  21.         console.info('getted by IDBKeyRange', customer.ssn, customer, event, this);
  22.  
  23.         if(customer.ssn == '111-11-1111') {
  24.  
  25.             customer.age = 6;
  26.  
  27.             // only for cursor
  28.             context.update(customer);
  29.         }
  30.  
  31.         context.result.push(customer);
  32.     });
  33.  

Свое видение о том, как должен был бы выглядеть асинхронный доступ к базе данных с такой архитектурой, я реализовал в indb.js.

Там ещё много вкусного, например, можно регистрировать события об изменении/создании объектов хранилища, на порядок проще удалять базу или хранилище.

PS: Для тех кто запустит код примера в FF, сейчас там есть любопытный баг в реализации движка IndexedDB. Если поменять customer.age = 6; на ++customer.age; то после пересоздания базы вы получите возраст Maria 60 лет, а не 41. Любопытно так же, что если перезапустить скрипт до отрабатываемой очистки по timeout в 5 секунд, то все будет Ok.

Автор: Sigura


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js