为什么在声明事件处理程序之前发出indexedDB请求?

dpiehjr4  于 2022-12-09  发布在  IndexedDB
关注(0)|答案(2)|浏览(155)

感觉这一定是一个愚蠢的问题,但我不明白在indexedDB中发出请求的基本原理。
为什么在定义事件处理程序之前发出请求?例如,在声明request.onsuccessrequest.onerror函数之前发出request = objectStore.add(data)。这是否正确?是否有可能在注册事件处理程序之前完成请求?
我把它比作创建一个图像元素,然后声明onload和onerror的事件处理程序,所有这些都是在将source属性设置为文件的位置并试图加载它之前完成的。因此,在发出请求之前,没有任何东西可以附加到事件。
请让我知道我在这里遗漏了什么。我一直在从indexedDB中写入和检索数据,没有任何问题,并且认为我的编码是正确的;但我想确保这是正确的,并将永远工作。

  • 谢谢-谢谢

重复响应

当我第一次开始阅读indexedDB时,我读过这个问题和答案,但完全忘记了。如果我在写这个问题之前再次找到它,我可能就不会提交它,而只是接受代码应该工作,不管我是否理解它。处理错误事件和事务中止是让我再次思考语句顺序的原因。
然而,在再次阅读了答案之后,我并没有足够的理解,只能接受它,并希望它永远有效。我并不是想挖苦你。从某种意义上说,我的有限能力让我感到困惑,因为我认为事件循环和纪元以及所有事情都是同时发生的。
在一个时期结束时(或者下一个时期开始时,无论你认为什么更容易理解),底层JS引擎返回并查看注册要执行的内容,然后几乎一次执行所有内容。
必须有一个执行顺序,否则无论异步与否都没有意义。我知道解释器在开始执行下一行代码之前不会等待任何同步进程完成。但是,同步语句是不是完全按照它们在代码中出现的顺序依次处理的,而异步语句是不是按照它们在代码中出现的顺序开始处理的,这样,如果一个异步进程很快出错,如果没有事先声明事件处理程序,就可能错过事件。事件处理程序不像函数声明那样被提升,是吗?这是我仍然感到困惑的部分。
在这个article由杰克阿奇博尔德的承诺,在介绍中,他提出了一个关于加载图像的例子,并写道:
不幸的是,在上面的例子中,有可能事件在我们开始监听之前就已经发生了,所以我们需要使用图像的“complete”属性来解决这个问题。

这并不能捕捉到那些在我们有机会去听之前就出错的图像;不幸的是,DOM并没有给予我们提供这样做的方法。而且,这是加载一个图像,如果我们想知道一组图像何时加载,事情会变得更加复杂。
这给人一种顺序很重要的印象,因此,在图像的情况下,如果可能,应该在声明所有事件处理程序之后分配源,以免错过听到的事件。对我来说,重要的部分是事件可以在事件处理程序被声明/注册之前发生。
在indexedDB中声明事件处理程序后,我试图遵循同样的模式发出请求,但似乎不可能,因为在发出请求之前没有任何东西可以附加事件。
即使所有的语句都是异步的,比如在MDN Web Docs的Using IndexedDB中的this示例,有些事情仍然相当令人困惑。objectStore.transaction.oncomplete是一个有趣的语句。我们等待objectStore被创建,然后才尝试向它写入数据。(我认为这被认为是不好的做法,在onupgradeneded事件中写入数据;所以,我们不使用该语句。)但令人困惑的是,为什么我们不担心在创建objectStore中的索引之前创建objectStore。如果所有内容都一次性处理,为什么createIndex语句不在createObjectStore语句启动的同时启动?如果createObjectStore语句在createIndex语句开始之前没有完成,是否应该需要事件处理程序,否则它会因为objectStore尚不存在而失败?
我知道它能工作,因为我一直在使用相同的代码模式,但我真的不明白它。

这两项--丢失事件的可能性和为什么在这个indexedDB示例中不需要事件处理程序--是我想要更好地理解的。我不知道这是否使我的问题有所不同,但是重复问题的答案并没有回答我这些问题。也许,我必须更好地理解JS引擎才能理解这些问题的答案。

const dbName = "the_name";

var request = indexedDB.open(dbName, 2);

request.onerror = function(event) {
  // Handle errors.
};
request.onupgradeneeded = function(event) {
  var db = event.target.result;

  // Create an objectStore to hold information about our customers. We're
  // going to use "ssn" as our key path because it's guaranteed to be
  // unique - or at least that's what I was told during the kickoff meeting.
  var objectStore = db.createObjectStore("customers", { keyPath: "ssn" });

  // Create an index to search customers by name. We may have duplicates
  // so we can't use a unique index.
  objectStore.createIndex("name", "name", { unique: false });

  // Create an index to search customers by email. We want to ensure that
  // no two customers have the same email, so use a unique index.
  objectStore.createIndex("email", "email", { unique: true });

  // Use transaction oncomplete to make sure the objectStore creation is 
  // finished before adding data into it.
  objectStore.transaction.oncomplete = function(event) {
    // Store values in the newly created objectStore.
    var customerObjectStore = db.transaction("customers", "readwrite").objectStore("customers");
    customerData.forEach(function(customer) {
      customerObjectStore.add(customer);
    });
  };
};

澄清/答复/评论

感谢您抽出宝贵时间回答我的问题并提供补充说明。
首先,我所说的“之前”只是指语句在脚本中出现的顺序。
我想我明白你的比喻,这是一个很好的比喻,只是我仍然不明白,为什么雇员从来不把工作交给秘书,直到第二天,当保证有秘书在那里接受它。
这听起来类似于javascript解释器,当它执行其编译脚本的等价物时,提升函数声明,这样在函数声明之前就可以在代码中调用函数。
看起来你的说法,用我的简单术语重申,JS引擎,在最终执行之前的某个点,分配事件处理程序(秘书)登记在一个更早的时期比一个请求因此,request语句出现在代码中相对于事件处理程序的什么位置并不重要,也就是说,只要它们被定义在相同的时期内。
JS引擎不知道请求何时完成,而只知道事件处理程序何时注册开始监听以及请求何时开始。只要JS引擎有一个进程来正确地排序这些步骤,而不依赖于语句在代码中出现的顺序,这样就不会错过事件,那么对我来说,这与提升函数声明没有什么不同,我真的不必再为完成我的任务而考虑太多。
然而,我还是想更好地理解什么是纪元,至少在知道语句是在同一个纪元内做出的这一点上。我在MDN Web Docs上的“并发模型和事件循环”的文章中没有看到任何提到纪元的内容。您能给我指一下您知道的好资源吗?

  • 谢谢-谢谢

最后备注

我是通过一个关于堆栈溢出的链接偶然发现这两个问题的,这个问题八年前就被问到了,回答的方式差不多,只是用了不同的术语;也就是说,javascript代码将“运行到完成”或具有运行到完成语义,而不是epochs。这个question让您参考这个文档,可以搜索“运行到完成”来阅读两个关于为什么在注册事件处理程序之前发出请求的设置中没有争用条件的交流。我有一本大卫Flanagan写的旧JavaScript书,在讨论JS“程序”的执行时,声明,因为JS具有单线程执行,所以永远不必担心竞争条件;但我不知道他指的是不是这种情况。
因此,这个问题在过去已经被问过和回答过多次了,我想我只是又一个新手,问了一个老问题,好像我是第一个想到这个问题的人,而且对JS是如何处理的没有足够的知识。
上面链接的文章“并发模型和事件循环”有一个简短的“运行到完成”部分;但直到阅读了上面链接的最后一个文档之后,我才明白它的含义。
我现在把它理解为一个函数中的所有代码都将在任何其他代码可以开始之前运行完成,这似乎有两种解释。
1.一个是当函数代码中的语句到达时,数据库上的异步请求被排队,但直到函数中的所有其他语句都运行完(包括之后声明的事件处理程序)后,才真正开始。
1.或者,根据上面最后一个链接的文档,异步请求可能在事件处理程序注册之前运行甚至完成,但是其完成通知将保留在队列中,直到函数中的其余语句运行并注册事件处理程序之后才会执行。
解释2似乎是准确的,但无论是哪种实际情况,它现在对我来说都有足够的意义,并解释了为什么秘书总是在雇员提交工作之前就在那里,以及为什么,即使雇员在一纳秒内完成工作,员工在第二天秘书保证到场接收后才提交工作。员工可以将工作-队列中的完整通知,但队列直到第二天才会发出通知让秘书听到。

感谢Josh对“纪元”的含义以及该术语如何在操作中发挥作用的额外解释。我接受了你的回答,并感谢你花时间将其全部写出来。
现在,我似乎理解了为什么可以在发出请求之后在代码中进行事件处理程序声明,但我仍然不理解为什么我们可以创建一个对象存储区,然后立即在该对象存储区上创建索引,而不必等到知道对象存储区已成功创建,除非它是同步的或者在versionchange事务/ onupgradeneeded事件中发生了其他特殊的事情。在MDNWebDocs对createObjectStore的描述中没有提到任何事件,也没有包含任何侦听器的示例;所以我就当没必要了。
再次感谢。

ogq8wdun

ogq8wdun1#

为什么在定义事件处理程序之前发出请求?
这不要紧。
例如,request = objectStore.add(data)是在request.onsuccess和request.onerror函数声明之前生成的,这对吗?
是的,这是正确的,因为这也无关紧要。
我会小心你在 * 之前使用 * 这个词。也许它对我的意义和对你的意义不同。我不知道。但也许这就是你绊倒的地方。
是否有可能在注册事件处理程序之前完成请求?
如果在发出请求的同一时期注册事件处理程序,则不可以。请求仅在以后的时期完成。
好的,我尝试用例子来解释(如果这不好的话,对不起!)。人格化通常是一种很好的教育技巧,而且比使用原始的技术术语更容易让人接受,所以让我们继续下去。
假设你是一个老板,有员工,你让一个员工帮你做一些工作,然后你让那个员工完成工作后向你的秘书汇报,然后你马上让那个员工去做其他的工作,然后你继续做自己的工作。而不用等那个员工完成他们的工作再回来汇报。你们基本上是同时在做工作。
现在,在这种情况下,如果你在给员工下达任务的时候没有秘书怎么办?嗯,没问题。在员工完成工作之前,甚至在员工还不知道向谁汇报之前,你就去雇了另一个秘书。这很好,因为员工只知道他们向你的秘书汇报。员工在被分配工作时不知道您的秘书是否存在,也不需要知道。秘书失踪并不妨碍员工开始工作。或者,你不知道,因为你甚至不想知道工作是否已经完成,你只是下了一个命令,相信员工会做好他们的工作,不管怎样。2你真正关心的是,如果你需要做一些其他的工作,而这些工作必须等到第一个项目完成后才能做,那么你只需要让他们向你的秘书汇报,这是另一回事。
假设你在给员工分配工作的时候已经有了一个秘书。这种情况和你在分配工作后不久,但还没有完成之前就去雇佣一个秘书的情况有什么区别?没有区别。
现在,让我们试着真正解决你的担忧。你的意思是,在你知道员工是否完成了任务之前,似乎不可能可靠地出去雇佣那个秘书。我认为这是一个严重的误解。完全有可能做到这一点。为什么?我想这不是一件容易理解的事情。
我将把这个比喻延伸一点,并强加一个奇怪的规则。无论你交给员工的项目多么简单,即使只是早上跑去给你买咖啡,他们也永远不会在同一天给你回复。他们总是会在稍后的一天完成工作,最早也是明天。他们甚至可能在你告诉他们后的一纳秒内完成工作。但他们永远不会马上给你或你的秘书回复,他们最早也要等到明天。
这意味着你有一整天的时间去雇佣那个在你给员工下订单时还不存在的秘书。只要你在明天之前做到了,你就很好了。那个秘书在员工明天回复的时候已经存在并为你工作了,并且能够收到员工发来的信息。
编辑对您添加的评论的回复:
是的,提升在很多方面都是相似的。事情可以按照不同的顺序发生,然后用代码编写。提升当然是同步的,所以它不是完美的相似,但无序方面仍然是相似的。
Epoch是我自己的一个词,我用它来表示事件循环的一次迭代。就像for循环使用i for i从0到2的情况一样,有3个epoch,迭代0,迭代1,迭代2。我之所以称它们为epoch,是因为它就像时间的分类。

在promise的情况下,它甚至可能是一个微任务。基本上,这些都是“实现”一次做多件事的方法。Node称之为tick,并有nextTick()之类的东西将代码执行推迟到循环的下一个tick。在一个时期内,事情按照它们被写入的顺序发生(并且特别地,提升全部在时期0中)。但是一些代码可以是异步的,并且因此跨时期发生,并且因此可以按照与其被写入的顺序不同的顺序运行。较早写入的代码可以在较晚的时期中发生。
当你提出一个请求时,它说,开始做这个事情,最早在下一个纪元给我回复,你必须在当前纪元结束前为请求注册处理程序。
有些代码,比如您的示例中提到的图像预加载器,必须考虑到它附加侦听器的时间太晚(图像被预加载到另一个时间轴中,有些图像可能已经被加载了,在某些浏览器中,这意味着load不会触发),所以它希望检查imageElement.complete来捕捉这种情况。在事件侦听器实现的其他情况下,一些调度器实现会为新添加的监听器触发事件,这些事件已经发生,而新的监听器在事件发生时没有监听。2但这并不是事件监听器实现的普遍特性,只是某些实现的特性。
在onupgradeneded中的transaction.oncomplete的例子中,这不是一个很好的例子,它在做它不需要做的事情。

oyxsuwqo

oyxsuwqo2#

这是您问题的技术答案:
https://html.spec.whatwg.org/multipage/webappapis.html#event-loops
JS并发模型与“run-to-completion”语义(在同一队列中没有事件的并行处理)协作。这意味着任何异步响应都将作为消息发布到窗口事件循环,并且您在请求之后看到的所有顺序代码都保证在异步响应处理开始之前执行。
也就是说,从可用性的Angular 来看,IndexDB API并没有以最具表达力的方式提供意图,并且来自其他具有抢先线程的语言,您可以原谅自己的困惑:-)

相关问题