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

时间:2018-07-18 05:48:42

标签: javascript indexeddb

感觉这肯定是一个愚蠢的问题,但我不了解在indexedDB中进行请求的基本知识。

为什么在定义事件处理程序之前发出请求?例如,在声明request = objectStore.add(data)request.onsuccess函数之前创建request.onerror。它是否正确?在事件处理程序注册之前,请求是否有可能完成?

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

请让我知道我在这里想念的东西。我一直在毫无问题地从indexedDB写入和检索数据,并认为我已经对其进行了正确编码。但我想确保这是正确的,并且将始终有效。

谢谢。

重复答复

前一段时间,当我第一次开始阅读有关indexedDB的内容时,我已阅读并回答了该问题,并完全忘记了它。如果我在写这个问题之前又找到了它,我可能不会提交它,而只是接受该代码应该可以算出我是否理解。处理错误事件和事务中止是让我重新考虑语句顺序的原因。

但是,在再次阅读答案后,除了仅仅接受并希望它始终有效之外,我对下一步的了解还不够。我不是要装傻。从某种意义上说,我的能力有限,使我无法想到事件循环和纪元,并且所有事情一次都发生了。

  

在纪元结束时(或下一个纪元的开始,您认为更容易理解的内容),底层JS引擎返回并查看要执行的注册内容,然后几乎一次执行所有操作。

必须有一个执行顺序,否则没有任何意义(无论是否异步)。我了解到,解释器在开始执行下一行代码之前不会等待任何异步过程完成。但是同步语句不是按照它们在代码中出现的顺序依次进行完全处理,而异步语句不是按照它们在代码中出现的顺序开始处理,因此,如果异步过程迅速出错,则如果没有事先声明事件处理程序?事件处理程序没有像函数声明那样悬挂,不是吗?这是我仍然感到困惑的部分。

在杰克·阿奇博尔德(Jake Archibald)关于承诺的article中,他在引言中提供了一个有关图像加载和书写的示例:

  

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

  

这不会捕获我们有机会收听之前出现错误的图像;不幸的是,DOM没有给我们一种方法。另外,这是在加载一个图像,如果我们想知道何时加载一组图像,事情会变得更加复杂。

给人的印象是顺序很重要,因此在图像的情况下,应尽可能在声明所有事件处理程序之后分配源,以免遗漏事件。对我来说,重要的一点是,可以在声明/注册事件处理程序之前发生一个事件。

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

即使所有语句都是异步的,例如在“使用IndexedDB的MDN Web文档”中的this示例中,也有些事情令人困惑。 objectStore.transaction.oncomplete是一个有趣的语句。在尝试向其写入数据之前,我们正在等待objectStore的创建。 (我认为这是一种不好的做法,在onupgradeneeded事件中写入数据;因此,我们不使用该语句。)但是令人困惑的是,为什么我们不担心在其中创建索引之前就创建了objectStore。如果所有内容都立即处理,为什么createIndex语句不是在createObjectStore语句启动的同时启动的?如果createObjectStore语句在createIndex语句开始之前未完成,是否不应该要求事件处理程序,否则事件处理程序会失败,因为objectStore还不存在?

我知道它是可行的,因为我一直在使用相同的代码模式,但是我真的不明白。

这两个项目-可能会丢失事件,以及为什么在这个indexDB示例中不需要事件处理程序-这是我想更好地理解的。我不知道这是否使我的问题有所不同,但是重复问题的答案无法为我解答。也许,我必须更好地理解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引擎在最终执行之前的某个时刻分配了要在比请求请求更早的时期注册的事件处理程序(秘书)。 (员工)完全触发事件的操作将完成。因此,请求语句相对于事件处理程序出现在代码中的位置无关紧要,也就是说,只要它们在同一时期内定义即可。

JS引擎不知道请求何时完成,只有在注册了事件处理程序以开始侦听以及请求开始时才知道。只要JS引擎具有独立于语句在代码中出现的顺序正确地排序这些步骤的过程,这样就不会遗漏事件,那么对我而言,这与悬挂函数声明没有什么不同,并且我没有为了完成任务,我真的不得不再三思。

但是,我仍然想更好地理解什么是一个纪元,至少是在知道语句是在同一纪元内做出的方面。我看不到MDN Web文档中“并发模型和事件循环”一文中的任何纪元。您介意将我带给您任何有用的资源吗?

谢谢。

最终提示

我在堆栈溢出时通过一个链接遇到了这两个项目。八年前曾问过同样的问题,但用相同的方式回答,但用的术语不同。也就是说,不是时代,而是javascript代码将“运行至完成”或具有运行至完成的语义。此question指向您参考此document,可以在其中搜索“运行完成”以阅读两次有关为何在注册事件处理程序之前进行请求的设置中不存在竞争条件的交流。 David Flanagan在讨论JS“程序”执行时所写的一本比较老的JavaScript书指出,由于JS具有单线程执行,因此,人们永远不必担心竞争条件。但我不知道他是否完全是指这种情况。

因此,这个问题过去曾被问过多次,我想我只是另一个新手,问一个老问题,好像我是第一个想到这个问题的人,并且对JS的处理方式没有足够的了解

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

我现在要说的是,函数中的所有代码将在其他任何代码可以开始之前运行完成,这似乎有两种解释。

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

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

解释2似乎是准确的解释,但是,无论哪种情况,这对我来说都已经足够了,并解释了为什么秘书在雇员提交工作之前总是在秘书那里,以及为什么,即使雇员在一纳秒内完成工作,直到第二天保证有秘书在场时,员工才会提交工作。员工可以将工作完成通知放在队列中,但是直到第二天,队列中才会有秘书听到的通知。

感谢乔什(Josh)对时代的含义以及该术语在操作中的工作方式的补充说明。我接受了您的回答,并感谢您抽出宝贵的时间将其全部写下。

现在,我似乎理解为什么为什么可以在代码中而不是在请求之后进行事件处理程序声明,所以我仍然不明白为什么我们可以创建一个对象存储,然后立即在该对象上创建索引对象存储,而不必等到我们知道对象存储已成功创建之后,除非它是同步的或在版本更改事务/ onupgradeneeded事件中发生其他特殊事件。 MDN Web Docs对createObjectStore的描述中没有提到任何事件,也没有包含任何侦听器的示例。所以;我只是假设它根本没有必要。

再次感谢。

1 个答案:

答案 0 :(得分:1)

  

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

没关系。

  

例如,在声明request.onsuccess和request.onerror函数之前创建request = objectStore.add(data)。它是否正确?

是的,因为没有关系。

对于您使用之前一词,我会小心谨慎。也许这对我来说意味着不同于对您的意义。我不知道但这也许就是让您绊倒的原因。

  

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

如果您在发出请求的同一时期注册事件处理程序,则为否。该请求仅在以后的时代完成。


好的,这是我尝试通过示例进行的解释(很抱歉,如果不好的话!)。拟人化通常是一种很好的教育技术,与使用原始的技术术语相比,它没有那么令人生畏,所以让我们继续吧。

假设您是老板,并且有员工。假设您要求一名员工为您做一些工作。然后,您要求该员工在完成工作时向您的秘书报告。在要求员工去做其他工作之后,您立即进行自己的工作,而无需等待该员工完成工作并向其报告。你们基本上都在同时工作。

现在,在这种情况下,如果您在向员工提出要求做某事的时候没有秘书,会发生什么?好吧,没问题。您可以在该雇员完成工作之前,甚至在该雇员甚至不知道该向谁汇报之前就聘请另一位秘书,这很好,因为所有雇员都知道他们要向您的秘书汇报。员工在分配工作时不知道您的秘书是否存在,也不需要知道。失踪的秘书并没有阻止该员工开始工作或理解要完成的工作。当员工完成工作时,您已经准备好了秘书并等待着。或者,您不这样做,因为您根本就不在乎承认工作是否真正完成,因此您只是发出命令并相信员工可以完成工作。您真的只关心让他们向秘书报告,如果您需要做一些其他工作,这些工作必须等到第一个项目完成后才能进行,这是另一个问题。

假设您在分配员工工作时已经有一位秘书。这种已经有秘书的情况与您在分配工作后不久但完成之前去雇用一个人的情况有什么区别?没有区别。

现在,让我们尝试真正解决您的问题。您的建议是,在您不知道员工是否完成任务之前,很难可靠地外出并雇用该秘书。我认为这是严重的误解。完全有可能这样做。这是为什么?我想这不是最容易掌握的东西。

我将稍微扩展这个隐喻,并强加一条奇怪的规则。无论您将项目交付给员工多么简单,即使只是在早上运行并为您喝咖啡,他们也永远不会在同一天回到您身边。他们总是会在明天的第二天最早完成工作。他们甚至可能会在您告诉他们一秒钟的时间后完成工作,但是他们永远不会立即回覆您或您的秘书,他们总是会最早被推迟到明天。

这意味着您要整天去雇用您下达该员工命令时不存在的秘书。只要您在明天之前这样做,就可以了。该秘书将存在,并在员工明天做出答复时为您工作,并将能够收到员工的信息。


编辑对您添加的评论的回复:

是的,起吊在很多方面都是相似的。事情可能以与编写代码不同的顺序发生。提升当然是同步的,因此并不是完美的相似性,但无序方面仍然相似。

Epoch只是我自己的话,我用于事件循环的单次迭代。就像在for循环中使用i从0到2的i的情况一样,共有3个时期,分别是迭代0,迭代1和迭代2。由于它们类似于时间类别,因此我称它们为时期。

在应许情况下,它甚至可能是微任务。在js worker情况下,它可能类似于线程(而worker是旧的child-iframe技术的新热点)。基本上,这些都是一次“意识到”一次做多事情的方式。 Node称它为“ tick”,并具有nextTick()之类的东西,它们将代码执行推迟到其循环的下一个tick。在单个时期内,事物按它们被写入的顺序发生(并且显着提升全部在时期0中)。但是某些代码可能是异步的,因此会跨越各个时期,因此可能以与编写时不同的顺序运行。较早编写的代码可能在以后的时代发生。

当您提出请求时,它会说,开始做这件事,并在下一个时代最早与我联系。您可以在当前纪元结束之前注册该请求的处理程序。

某些代码(例如您的示例中提到的图像预加载器的情况)必须考虑到它太晚附加了侦听器(图像是在备用时间轴中预加载的,而某些图像可能已经被加载,某些情况下浏览器,这意味着不会触发加载),因此它想检查imageElement.complete来捕获这种情况。在事件侦听器实现的其他情况下,某些调度程序实现会将事件触发给新添加的侦听器,以用于在事件发生时新侦听器未侦听的事件。但这不是事件侦听器实现的通用特征,而只是某些实现的特征。

对于transaction.onupgrade中需要完成的事情,那不是一个很好的例子。它正在做不需要做的事情。