Правильная структура кода транзакции изменения версии в событии onupgradeneeded
У меня возникают проблемы с пониманием того, сколько кода можно поместить в событие onupgradeneeded и как обеспечить, чтобы все индивидуальные асинхронные изменения в базе данных в этом коде всегда выполнялись до выполнения транзакции.
Если я правильно понимаю спецификацию, транзакция режима "versionchange" автоматически создается, когда в запросе на открытие базы данных запускается событие onupgradeneeded.
Таким образом, весь код, написанный внутри события onupgradeneeded, считается одной транзакцией; и я предполагаю, что если он достигает oncomplete, он запускает событие onsuccess открытого запроса, а если он достигает onerror, то он запускает событие onerror открытого запроса.
Я смущен тем, насколько сложным может быть этот код.
Например, безопасно ли иметь внутри транзакции другое асинхронное событие, такое как "objectStore.transaction.oncomplete = function(event) {}", чтобы дождаться создания хранилища объектов перед попыткой записи в него?
Или следует выполнить запись данных в хранилище объектов, созданное в транзакции с обновлением onupgraded, в событии onsuccess открытого запроса, в котором он наверняка уже создан?
И, как показано в более старом примере в веб-документах MDN, должно ли быть событие db.onerror внутри события onupgradeneeded? https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase
Другой пример относится к моей базе данных. База данных имеет набор портфелей, в которых каждый портфель имеет хранилище объектов, такое как port_data_3, и переменное количество хранилищ модулей, таких как module_3.2, указывающее второй модуль третьего портфеля. Кроме того, существует одно хранилище объектов port_key, которое содержит одну запись на портфель, содержащую имя портфеля и его уникальный ключ, который в этом примере равен 3.
Если пользователь решает удалить портфель 3 из базы данных, требуется изменение версии, которое включает в себя три основных этапа. 1) Отдельная строка для ключа 3 в хранилище port_key должна быть удалена; 2) хранилище port_data_3 должно быть удалено; и 3) все магазины с именем, которое начинается с 'module_3.' должен быть удален.
Что-то вроде d = db.objectStoreNames, l = d.length, можно использовать для циклического обхода d.item (i), чтобы определить, какие хранилища следует удалить. Но будет ли транзакция всегда оставаться открытой до тех пор, пока все эти отдельные удаления не завершатся или не завершатся неудачно?
Точно так же добавление / удаление портфелей и добавление / удаление модулей в портфеле требуют изменения версии. Безопасно ли иметь одну открытую функцию, чтобы код для всех этих типов изменения версии находился в одном более сложном событии onupgradeneeded, или было бы лучше иметь отдельные открытые функции для типа изменения каждой версии, чтобы код события onupgradeneeded максимально просто для каждого?
Я полагаю, что часть моей путаницы связана с тем, что транзакция состоит из нескольких отдельных асинхронных процессов, и я не контролирую, как они группируются в одно событие транзакции. Я должен был бы использовать обещания или Promise.all или асинхронные функции или генераторы, чтобы сделать что-то подобное в остальной части кода. И это заставляет мой маленький мозг чувствовать уверенность, что я не пропущу какую-то ошибку, которая возникает поздно.
Я подумал, что будет безопаснее, если открытый запрос будет заключен в обещание, а обещание будет помещено в попытку / получение с ожиданием; но это по-прежнему не меняет того, как onupgradeneeded и его транзакция смены версий определяют, были ли все эти асинхронные процессы успешными или хотя бы один из них завершился неудачно.
Спасибо за любые указания и объяснения, которые вы можете предоставить.
Ответ на ответ от Джоша
Спасибо за ваш очень подробный ответ. Это мне очень помогает.
Это также помогает мне понять ваш ответ на один из моих вопросов еще в мае - возможно, самый первый, который я представил здесь - который я на самом деле не понял в то время. Вопрос заключался в том, как отобразить многоуровневый объект на indexedDB для лучшей эффективности.
Я думаю, что я понимаю, теперь, в свете вашего ответа на этот вопрос. Я могу изменить структуру базы данных так, чтобы мне никогда не приходилось обновлять базу данных из-за того, что пользователь добавляет портфели и модули. И, как вы рекомендовали в своем ответе на этот предыдущий вопрос, три необходимых ключа - портфель, модуль и элемент в модуле - могут представлять собой три индекса, а комбинация этих трех также может быть индексом, поскольку это будет запрос чаще всего нужен. Фактически, комбинация этих трех является единственным уникальным идентификатором, который у меня есть, если только я не позволю браузеру сгенерировать его. Таким образом, вместо переменного числа нескольких хранилищ объектов модулей, добавление каждого из которых требует обновления базы данных, будет эквивалент одного хранилища объектов модуля, которое содержит предыдущие объекты данных во всех портфелях и во всех модулях. И то же самое будет сделано, чтобы объединить несколько магазинов на уровне портфеля в одном.
Это упрощает эту базу данных и устраняет необходимость в большей части обновленного кода / транзакции, о которых я так беспокоился. С этими изменениями, я думаю, я наконец смогу вернуться к обновлению программы, чтобы использовать indexedDB вместо localStorage и, возможно, даже снова начать спать по ночам. В этом случае само кодирование - это не сложная часть, а определение эффективной структуры базы данных.
Спасибо также за информацию о том, как лучше планировать будущие вопросы.
1 ответ
- Вы можете иметь кучу того, что вы называете "асинхронными" вещами в обновленном приемнике событий. Безопасно прослушивать полное событие транзакции versionchange из обработчика upgradedeneeded, хотя я бы добавил, что это не стоит.
- Однако небезопасно делать что-то вроде ожидаемого
fetch
позвонить в onupgradeneeded. Во время выполнения транзакция будет завершена до разрешения вызова, и все операции с очередями завершатся неудачно. - Транзакция может иметь несколько запросов.
- Транзакция будет оставаться открытой, пока есть ожидающие запросы.
- Транзакция обычно остается открытой до конца текущей эпохи цикла обработки событий, а иногда и чуть дольше после этого.
- Транзакция автоматически завершает очень короткий период времени после того, как обнаруживает, что нет ожидающих запросов.
- Попытка поставить в очередь асинхронный вызов как
fetch
в то время как транзакция открыта, и затем ждать, пока выборка не завершится, и затем выполнить вставку в этой открытой транзакции, не будет работать, потому что транзакция будет завершена до этого, потому что эта выборка не разрешается до самого раннего в начало следующей эпохи цикла обработки событий, но транзакция разрешается раньше, поскольку новые запросы не обнаружены. - Транзакции в основном настроены на тайм-аут с самого начала. Каждый раз, когда вы делаете запрос, вы поддерживаете их чуть дольше.
- Транзакция разрешается / рассчитывается / завершается после завершения запроса. Не только когда его запрос начнется. Запрос, который еще не завершен, все еще находится на рассмотрении. Транзакция с ожидающими запросами не будет завершена (тайм-аут).
- Запрос может быть успешно завершен или с ошибкой, оба обрабатывают запрос.
- Транзакция с несколькими ожидающими запросами, в случае сбоя одного запроса, может прервать другие ожидающие запросы и просто завершиться с ошибкой. Эта ошибка одного запроса становится ошибкой транзакции. Затем транзакция заканчивается с этой ошибкой.
Обратите внимание на некоторые аккуратные формулировки документации для функций, которые вы найдете в таких местах, как Mozilla Developer Network (MDN). Например store.delete(thing)
, delete
Функция создает новый запрос в транзакции, с которой связано хранилище. Это совершенно безопасная асинхронная вещь. Вы можете создать любое количество дополнительных запросов, как это. Вам не нужно ждать завершения транзакции, чтобы добавить новый запрос. Вам не нужно ждать окончания других запросов, чтобы начать новый запрос. Вам не нужно ждать завершения транзакции, прежде чем начинать транзакцию (с одним предупреждением - специальная транзакция изменения версии в onupgradeneeded).
Транзакция - это просто группировка запросов. Это группа, которая помогает вам сказать, что запросы живут и умирают вместе как группа. Если какой-либо один запрос терпит неудачу, вся группа терпит неудачу. В этом весь смысл сделки. Это настолько полезно, что indexedDB не предоставляет вам другого способа делать запросы, вы вынуждены использовать транзакцию даже для одного запроса. Если вы работали с базой данных SQL, возможно, вы столкнулись с синтаксисом вроде START TRANSACTION; SELECT ...; END TRANSACTION;
, То же самое и здесь. За исключением того, что indexedDB не позволит вам сделать это SELECT ...
вне транзакции, что вы смогли сделать в SQL. Кроме того, indexedDB не позволяет вам явно завершить транзакцию самостоятельно. Вы завершаете транзакцию indexedDB, просто решив не создавать больше запросов и разрешив ей тайм-аут вскоре после этого.
Что касается вашего общего проекта программирования, я бы настоятельно рекомендовал выбрать проект, в котором вам не нужно менять схему базы данных по мере изменения ваших данных и событий, происходящих в вашем приложении / мире. Обычно схема должна быть постоянной. Если вам часто приходится создавать и удалять хранилища объектов, что является нормальным вопросом работы вашего приложения, я бы серьезно переосмыслил дизайн.
Кроме того, хотя вы можете вставлять данные изнутри onupgradeneeded, вы, как правило, должны делать это из обработчика события успеха для IDBOpenRequest. Это обычное правило (не формальное), когда изменения в данных включаются в onsuccess, а изменения в схеме - в onupgradeneeded. Если в Интернете вы сталкиваетесь с примерами, в которых изменения данных происходят в onupgradeneeded, я советую вам внимательно прочитать, в этих примерах это часто делается просто и быстро для удобства, а не для правильного проектирования приложения.
Кроме того, вы получите лучшие ответы, если разберете этот очень широкий вопрос на более мелкие фрагменты, которые подчеркнут ваши области путаницы, и покажите пример кода, который работает неожиданно, или с комментарием в нем, выделив часть, которую вы не знаете, как сформулировать.