onupgradeneeded 事件中 versionchange 事务的正确代码结构

proper code structure of versionchange transaction in onupgradeneeded event

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

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

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

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

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

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

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

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

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

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

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

我想我的部分困惑是由于一个事务由多个独立的异步进程组成,而我无法控制它们如何分组到一个事务事件中。我必须使用 promises 或 Promise.all 或异步函数或生成器在其余代码中做类似的事情。而且让我的小脑袋很费力地确信我不会错过某些迟来的错误。

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

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

对 Josh 回答的回复

感谢您非常详细的回答。对我帮助很大。

这也有助于我理解您在 5 月份对我的一个问题的回答——也许是我在这里提交的第一个问题——当时我并没有真正理解后半部分。问题是 .

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

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

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

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

请注意您在 Mozilla 开发人员网络 (MDN) 等地方找到的功能文档的一些谨慎措辞。例如 store.delete(thing)delete 函数 在与商店关联的事务中创建一个新请求。这是一件非常安全的 async 事情。您可以像这样创建任意数量的附加请求。您无需等待交易完成即可添加新请求。您不必等待其他请求完成即可启动新请求。您不必在开始交易之前等待交易完成(有一个警告,onupgradeneeded 中的特殊版本更改交易)。

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

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

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

顺便说一句,如果您将这个非常广泛的问题分解成更小的部分来突出您的困惑区域,并显示出乎意料的示例代码,或者在其中突出显示您的部分的评论,您将获得更好的答案不知道怎么制定。