首页 > 解决方案 > onupgradeneeded 事件中版本更改事务的正确代码结构

问题描述

我无法理解在 onupgradeneeded 事件中可以放置多少代码,以及如何确保该代码中对数据库的所有单独异步更改始终在事务完成之前完成。

如果我正确理解规范,则在打开数据库的请求中触发 onupgradeneeded 事件时会自动创建模式为“versionchange”的事务。

因此,在 onupgradeneeded 事件中编写的所有代码都被视为单个事务;我假设如果它达到 oncomplete 它会触发打开请求的 onsuccess 事件,如果它达到 onerror 然后它会触发打开请求的 onerror 事件。

我对代码的复杂程度感到困惑。

例如,在事务中存在另一个异步事件是否安全,例如“objectStore.transaction.oncomplete = function(event) {}”在尝试写入之前等待对象存储创建?

或者是否应该在打开请求的 onsuccess 事件中执行在 onupgradeneeded 事务中创建的对象存储的数据写入,在该事件中确保已经创建?

而且,正如在 MDN web 文档中的一个较旧示例中发现的那样,onupgradeneeded 事件中是否应该再有一个 db.onerror 事件?https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase#setVersion()_.0A.0AD不推荐使用

另一个示例特定于我的数据库。该数据库具有一组投资组合,其中每个投资组合具有诸如port_data_3之类的对象存储和诸如module_3.2之类的可变数量的模块存储,指示第三个投资组合的第二个模块。此外,还有一个 port_key 对象存储,它为每个投资组合保存一条记录,其中包含投资组合名称及其唯一键,在本示例中为 3。

如果用户决定从数据库中删除投资组合 3,则需要进行版本更改,其中包括三个主要步骤。1)必须删除port_key存储中key 3的单行;2) 必须删除port_data_3 存储;3) 所有名称以“module_3”开头的商店。必须删除。

d = db.objectStoreNames, l = d.length 之类的东西可用于循环 d.item(i) 以确定应删除哪些存储。但是在所有这些单独的删除完成或失败之前,事务是否会一直保持打开状态?

类似地,投资组合的添加/删除和投资组合中模块的添加/删除需要版本更改。拥有一个开放函数是否安全,以便所有这些版本更改类型的代码都在一个更复杂的 onupgradeneeded 事件中,或者最好为每个版本更改类型设置单独的开放函数,以便 onupgradeneeded 事件代码每个人都尽可能简单吗?

我想我的部分困惑是由于一个事务由许多单独的异步进程组成,我无法控制它们如何被分组到一个事务事件中。我必须使用 Promise 或 Promise.all 或异步函数或生成器在其余代码中执行类似的操作。我的小脑袋很费力,要确信我不会错过某种迟到的错误。

我认为如果将打开的请求包装在一个承诺中,并将承诺放在一个带有等待的 try/catch 中,会感觉更安全;但这仍然不会改变 onupgradeneeded 及其 versionchange 事务如何确定所有这些异步进程是成功还是至少一个失败。

感谢您提供的任何指导和解释。

回应乔希的回答

谢谢你非常详细的回答。它对我有很大帮助。

它还有助于我理解你对我五月份的一个问题的回答——也许是我在这里提交的第一个问题——当时我并没有真正理解后面的部分。问题是如何将多级对象映射到 indexedDB 以获得最佳效率

鉴于您对这个问题的回答,我想我现在明白了。我可以更改数据库结构,这样我就不需要因为用户添加投资组合和模块而升级数据库。而且,正如您在对前面问题的回答中所建议的那样,所需的三个键——投资组合、模块和模块下的项目——可能是三个索引,三者的组合也可能是一个索引,因为那将是最常需要的查询。事实上,这三者的组合是我唯一拥有的唯一标识符,除非我让浏览器生成一个。因此,代替可变数量的多个模块对象存储,每个添加都需要数据库升级,而是等效于一个模块对象存储,其中包含所有投资组合和所有模块的先前数据对象。

这简化了该数据库,并消除了我非常关心的大部分 onupgradeneeded 代码/事务的需要。通过这些更改,我想我终于可以重新开始更新程序以使用 indexedDB 而不是 localStorage,甚至可能再次开始睡觉。在这种情况下,编码本身并不是具有挑战性的部分,而是确定有效的数据库结构。

也感谢您提供有关如何更好地安排未来问题的信息。

标签: javascriptindexeddb

解决方案


  • 你可以在 upgradeneeded 事件监听器中有一堆你称之为“异步”的东西。从 upgradeneeded 处理程序中侦听 versionchange 事务的完整事件是安全的,尽管我会补充说这是不值得的。
  • fetch但是,在 onupgradeneeded中执行类似于等待调用的操作并不安全。在运行时,事务将在调用解决之前完成,并且所有排队的操作都将失败。
  • 一个事务可以有多个请求。
  • 只要有待处理的请求,事务就会保持打开状态。
  • 事务通常会保持打开状态,直到事件循环的当前 epoch 结束,有时会在此之后稍长一些。
  • 事务在检测到没有待处理的请求后会在很短的时间内自动完成。
  • 尝试在事务打开时将异步调用排队fetch,然后等待获取解决,然后对该打开的事务执行插入,这是行不通的,因为事务将在此之前完成,因为该获取没有t 最早在事件循环的下一个 epoch 开始时才解决,但事务更早地解决,因为没有检测到新请求。
  • 事务基本上从一开始就设置为超时。每次您提出请求时,您都会使它们的存活时间延长一点。
  • 事务在其请求完成时解决/解决/完成。不仅仅是在它的请求开始时。尚未完成的请求仍处于待处理状态。具有待处理请求的事务将不会完成(超时)。
  • 请求可以成功完成,也可以出错,两者都解决了请求。
  • 具有多个待处理请求的事务,其中一个请求失败,可能会中止其他待处理请求并以错误结束。一个请求错误成为事务的错误。然后事务以该错误结束。

请注意您在 Mozilla 开发者网络 (MDN) 等地方找到的功能文档的一些谨慎措辞。例如store.delete(thing). 该delete函数在与存储关联的事务中创建一个新请求。这是一个非常安全的异步操作。您可以像这样创建任意数量的附加请求。您无需等待事务完成即可添加新请求。您不必等待其他请求完成即可开始新请求。您不必在开始事务之前等待事务完成(需要注意的是,onupgradeneeded 中的特殊版本更改事务)。

事务只是一组请求。它是一个分组,可以帮助您说请求作为一个群体共同生存和死亡。如果任何一个请求失败,则整个组都失败。这就是交易的全部意义所在。它非常有用,以至于 indexedDB 没有为您提供其他方式来发出请求,即使只有一个请求,您也被迫使用事务。如果您使用过 SQL 数据库,可能会遇到类似START TRANSACTION; SELECT ...; END TRANSACTION;. 这里也一样。除了 indexedDB 不允许您SELECT ...在事务之外执行此操作,而您可以在 SQL 中执行此操作。此外,indexedDB 不允许您自己显式结束事务。您只需决定不创建更多请求并允许它在此后不久超时即可完成 indexedDB 事务。

关于您的整体编程设计,我强烈建议您选择一种设计,当您的数据发生变化以及您的应用程序/世界中发生的事情时,您不需要更改数据库架构。通常,模式应该是恒定的。如果您发现自己需要频繁地创建和删除对象存储作为应用程序的正常操作问题,我会认真重新考虑设计。

此外,虽然您可以从 onupgradeneeded 中插入数据,但您通常应该从 IDBOpenRequest 的成功事件处理程序中执行此操作。这是一个常规规则(不是正式规则),对数据的更改在成功时进行,对模式的更改在需要升级时进行。如果您在网络上遇到 onupgradeneeded 发生数据更改的示例,建议您仔细阅读,这些示例中通常只是快速完成并且出于方便,而不是适当的应用程序设计。

顺便说一句,如果您将这个非常广泛的问题分解成更小的部分,突出您的困惑领域,并显示出乎意料的示例代码,或者在其中添加注释突出您不知道如何操作的部分,您将获得更好的答案制定。


推荐阅读