代码之家  ›  专栏  ›  技术社区  ›  Gary

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

  •  1
  • Gary  · 技术社区  · 7 年前

    我觉得这一定是个愚蠢的问题,但我不明白在indexeddb中提出请求的基本原理。

    为什么在定义事件处理程序之前发出请求?例如, request = objectStore.add(data) 是在 request.onsuccess request.onerror 函数已声明。这是对的吗?是否可以在注册事件处理程序之前完成请求?

    我将其与创建一个image元素进行比较,然后声明onload和onerror的事件处理程序,所有这些都是在将source属性设置为文件位置并尝试加载它之前完成的。但是在发出请求之前不能创建请求“元素”;因此,在发出请求之前,没有任何东西可以附加事件。

    请让我知道我错过了什么。我一直在毫无问题地从indexeddb中编写和检索数据,并且认为我已经对其进行了正确的编码;但是我想确保这是正确的,并且将始终有效。

    谢谢您。

    重复响应

    我以前读过这个问题和答案,当我第一次开始读indexeddb的时候,我完全忘记了它。如果我在写这个问题之前再次找到它,我可能不会提交它,而只是接受代码应该能解决我是否理解它。处理错误事件和事务中止使我重新考虑语句顺序。

    然而,在再次阅读答案后,我没有足够的理解,除了接受它,并希望它将永远有效。我不是想成为狙击手。从某种意义上说,我有限的思考事件循环和时代的能力让我感到困惑,所有事情都是同时发生的。

    在新纪元结束时(或者下一个纪元开始时,无论您认为什么更容易理解),底层js引擎会返回并查看注册要执行的内容,然后几乎同时执行所有内容。

    必须有一个执行顺序,否则没有任何意义,不管异步与否。我理解解释器在开始执行下一行代码之前不会等待anysnchronous进程完成。但是同步语句不是按照它们在代码中的出现顺序依次处理的吗?异步语句不是按照它们在代码中的出现顺序开始的吗?这样,如果异步进程很快出错,如果事件处理程序没有提前声明,则可能会错过事件?事件处理程序不像函数声明那样被提升,是吗?这是我仍然觉得困惑的部分。

    在这个 article 作者jake archibald on promises,在引言中,他给出了一个有关图像加载的示例,并写道:

    不幸的是,在上面的例子中,事件可能是在我们开始监听之前发生的,所以我们需要使用图像的“complete”属性来解决这个问题。

    在我们有机会聆听之前,这不会捕捉到错误的图像;不幸的是,dom并没有给我们提供这样做的方法。另外,这是加载一个图像,如果我们想知道何时加载了一组图像,事情会变得更加复杂。

    这给人的印象是顺序很重要,这样,在图像的情况下,在可能的情况下,应该在声明所有事件处理程序之后分配源,以免错过听证会事件。对我来说,重要的是事件可以在事件处理程序声明/注册之前发生。

    在indexeddb中声明事件处理程序之后,我试图遵循发出请求的相同模式,但似乎不可能,因为在发出请求之前,没有任何东西可以附加事件。

    即使所有语句都是异步的,例如 this 在mdn web文档中使用indexeddb的例子中,有些事情仍然相当混乱。这个 objectStore.transaction.oncomplete 是一个有趣的陈述。我们正在等待创建objectstore,然后再尝试向其写入数据。(我认为在onupgradeneeded事件中写入数据被认为是不好的做法;因此,我们不使用该语句。)但令人困惑的是,我们为什么不担心在创建objectstore中的索引之前创建它。为什么createindex语句不在createobjectstore语句启动的同时启动,如果所有内容都是一次处理的话?如果CealObjuttSturt语句在CealEdTeX语句开始之前没有完成,那么不应该要求事件处理程序还是因为ObjtStury还不存在而失败?

    我知道这很有效,因为我一直在使用相同的代码模式,但我真的不明白。

    这两项——可能会错过事件,以及为什么在这个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);
        });
      };
    };
    

    澄清/答复/评论

    感谢您抽出时间回答我的问题,并提供额外的解释。

    首先,我所说的“before”只是指语句在脚本中出现的顺序。

    我想我遵循你的比喻,这是一个很好的比喻。我只是不清楚为什么员工们直到第二天才把他们的工作交给秘书,因为第二天秘书肯定会来接手。

    这听起来类似于这样一个事实:javascript解释器在执行与编译脚本相当的任务时,会提升函数声明,以便在进行函数声明之前可以在代码中调用函数。

    你的话,用我简单的话说,js引擎,在最终执行之前的某个时刻,指派事件处理程序(秘书)在一个比请求(雇员)完成的时间更早的纪元中注册。因此,在代码中,请求语句相对于事件处理程序出现在哪里并不重要,也就是说,只要它们是在同一个epoch中定义的。

    js引擎不知道请求何时完成,但只知道事件处理程序何时注册以开始侦听以及请求何时开始。只要js引擎有一个独立于语句在代码中出现的顺序对这些步骤进行适当排序的过程,这样就不会错过事件,那么对我来说,这与提升函数声明没有什么不同,我不必再为它多想,就可以完成我的任务。

    不过,我还是想更好地理解什么是纪元,至少是在知道这些陈述是在同一个纪元内作出的情况下。在mdn web文档中关于“concurrecny模型和事件循环”的文章中,我没有提到任何纪元。你能给我指一下你知道的好的资源吗?

    谢谢您。

    最后说明

    我是通过一个链接找到这两个项目的,这里是堆栈溢出。这个问题在8年前就被提出过,答案大致相同,但术语不同;也就是说,javascript代码将“运行到完成”或已经运行到完成语义,而不是时代。这个 question 指给你看这个 document 它可以搜索“run to completion”来读取两个交换,说明为什么在注册事件处理程序之前,在该设置中没有竞争条件来发出请求。david flanagan在讨论js“程序”的执行时,写了一本旧的javascript书,书中说,由于js是单线程执行的,人们永远不必担心竞争条件;但我不知道他是否准确地提到了这种情况。

    因此,这个问题在过去已经被多次问过和回答过,我想我只是另一个新手,问了一个老问题,好像我是第一个想到这个问题的人,而且对js如何处理没有足够的知识。

    上面链接的文章“并发模型和事件循环”有一个简短的“运行到完成”部分;但是直到阅读了上面链接的最后一个文档之后,我才理解它的含义。

    我现在认为这意味着一个函数中的所有代码都将在任何其他代码开始之前运行完成,这似乎有两种解释。

    1. 一种是,当函数代码中的语句到达时,数据库上的异步请求将排队,但在函数中的所有其他语句(包括随后声明的事件处理程序)运行之前不会真正开始。

    2. 或者,根据上面最后一个链接的文档,异步请求可以在注册事件处理程序之前运行甚至完成,但其完成通知将保留在队列中,直到运行函数中的其余语句并注册事件处理程序之后才会执行。

    解释2似乎是正确的,但是,不管是哪种情况,它现在对我来说都有足够的意义,并解释了为什么在员工提交工作之前秘书总是在那里,以及为什么即使员工在纳秒内完成工作,员工也要等到第二天秘书保证能收到它。员工可以将工作完成通知放入队列,但队列直到第二天才会发出通知,让秘书听到。

    谢谢你,乔希,关于什么是时代以及这个术语是如何运作的额外解释。我接受了你的回答,感谢你抽出时间把它全部写出来。

    现在,我似乎明白了为什么事件处理程序声明可以在代码中比请求的发出时间晚些,但我仍然不明白为什么我们可以创建一个对象存储,然后立即在该对象存储上创建一个索引,而不必等到我们知道对象存储已成功创建,除非它是同步的或在versionchange事务/onupgradeneeded事件中发生了其他特殊事件。createobjectstore的mdn web文档描述中没有提到任何事件,也没有包含任何监听器的示例;因此,我假设这是不必要的。

    再次感谢。

    1 回复  |  直到 7 年前
        1
  •  2
  •   Josh    7 年前

    为什么在定义事件处理程序之前发出请求?

    这无关紧要。

    例如, request = objectStore.add(data) 在声明request.onSuccess和request.onerror函数之前进行。这是对的吗?

    是的,这是正确的,因为这同样无关紧要。

    我会小心你用这个词的 之前 是的。也许这对我来说意味着不同于对你的意义。我不知道。但也许这就是绊倒你的原因。

    是否可以在注册事件处理程序之前完成请求?

    如果在发出请求时在同一个epoch中注册事件处理程序,则为“否”。请求只在以后的epoch中完成。


    好的,这是我试图用例子来解释的(如果这不好的话很抱歉!)人格化通常是一种很好的教育技巧,而且比使用原始的技术术语更不吓人,所以让我们继续吧。

    假设你是老板,有员工。假设你让一个员工为你做一些工作。然后,你让那个员工在完成工作后向你的秘书汇报。在要求员工去做其他的工作之后,你就可以继续自己的工作,而不必等待员工完成工作并报告。你们两个基本上是同时工作的。

    现在,在这种情况下,如果你在向员工提出做某事的请求时没有秘书,会发生什么?好吧,没问题。你要在员工完成工作之前,甚至在员工知道该向谁汇报之前,再去雇用另一名秘书,这很好,因为员工只知道他们向你的秘书汇报。员工在分配工作时不知道你的秘书是否存在,而不需要知道。失踪的秘书没有阻止那个雇员开始工作,也没有阻止他理解要做的工作。当员工完成他们的工作时,你已经有了一个秘书在等着你。或者,你不知道,因为你甚至都不知道工作是否真的完成了,你只是发出命令,相信员工能完成他们的工作,不管怎样。你真正关心的是,如果你需要做一些其他的工作,必须等到第一个项目完成后才能让他们向你的秘书汇报,这是另一个问题。

    假设你分配给员工工作的时候已经有了一个秘书。这种已经有了秘书的情况和你在分配工作后不久,但在工作完成之前去雇用秘书的情况有什么区别?没有区别。

    现在,让我们努力解决你的问题。你的建议是,在你知道员工是否完成了他们的任务之前,似乎不可能可靠地出去雇佣那个秘书。我认为这是严重的误解。完全有可能做到这一点。为什么?我想这不是最容易掌握的。

    我要把这个比喻延伸一点,并强加一个奇怪的规则。不管你把项目交给员工有多简单,即使只是早上跑去给你拿咖啡,他们也永远不会在同一天回到你身边。他们总是在晚些时候完成工作,最早是明天。他们甚至可能在你告诉他们的一瞬间内完成工作,但他们永远不会马上给你或你的秘书回信,他们总是最早推迟到明天。

    这意味着你有一整天的时间去雇佣那个在你给员工订单时不存在的秘书。只要你明天之前做,你就好。当员工明天回复时,秘书会为你工作,并能收到员工的信息。


    编辑对添加评论的响应:

    是的,吊装在很多方面都很相似。事情可以按照不同的顺序发生,然后用代码编写。当然,提升是同步的,所以它不是一个完美的相似性,但无序方面仍然是相似的。

    epoch只是我自己的一个词,用于事件循环的一次迭代。就像for循环使用i for i从0到2一样,有3个阶段,迭代0、迭代1和迭代2。我把它们叫做时代,因为它就像时间的类别。

    在承诺的情况下,它甚至可能是一个微任务。在js worker的情况下,它可能是线程式的(workers是旧的child iframe技术的新热点)。基本上,这些都只是“意识到”一次做不止一件事的方法。node称之为tick,并有如下内容 nextTick() 将代码执行推迟到下一个循环周期。在一个纪元内,事物按照它们被书写的顺序发生(特别是提升都在纪元0中)。但有些代码可能是异步的,因此会在不同的时期发生,因此可能会以不同的顺序运行。早期编写的代码可能会在以后的时代发生。

    当你提出要求时,它会说,开始做这件事,并在下一个时代尽早给我回信。在当前纪元结束之前,您可以为请求注册处理程序。

    一些代码,例如在您的示例中提到的图像预加载程序的情况下,必须考虑到它附加侦听器的时间太晚(图像正在以备用时间线预加载,有些可能已经加载,在某些浏览器中这意味着加载不会启动),因此它希望检查imageelement.complete以捕获这种情况。在事件侦听器实现的其他情况下,对于事件发生时新侦听器未侦听的已发生事件,某些调度程序实现将向新添加的侦听器激发事件。但这不是事件侦听器实现的通用特性,只是某些实现的特性。

    在onupgradeneeded内部的transaction.oncomplete中,这并不是一个很好的例子。它在做不需要做的事情。