Gar*_*ary 7 javascript indexeddb
感觉这一定是一个愚蠢的问题,但我不明白在 indexedDB 中发出请求的一些基本知识。
为什么在定义事件处理程序之前发出请求?例如,是在声明和函数request = objectStore.add(data)之前创建的。它是否正确?请求是否有可能在事件处理程序注册之前完成?request.onsuccessrequest.onerror
我将其与创建图像元素进行比较,然后声明 onload 和 onerror 的事件处理程序,所有这些都是在将 source 属性设置为文件位置并尝试加载它之前进行的。但是在发出请求之前不能创建请求“元素”;因此,在发出请求之前,没有任何事件可以附加到。
请让我知道我在这里缺少什么。我一直在毫无问题地从indexedDB中写入和检索数据,并且认为我已经正确编码了;但我想确保这是正确的并且永远有效。
谢谢。
重复响应
我不久前读过这个问题和答案,当时我第一次开始阅读有关 indexedDB 的文章,然后完全忘记了它。如果我在写这个问题之前再次找到它,我可能不会提交它,并且只会接受代码应该能够正常工作,无论我是否理解它。处理错误事件和事务中止让我再次考虑语句顺序。
然而,在再次阅读答案后,我除了接受它并希望它永远有效之外,还没有足够的理解。我并不是想挖苦。从某种意义上说,我对事件循环和纪元的思考能力有限,并且所有事情都同时发生,这只是令人困惑。
在纪元结束时(或者下一个纪元开始时,无论你认为更容易理解什么),底层 JS 引擎会返回并查看注册要执行的内容,然后几乎立即执行所有内容。
必须有一个执行顺序,否则无论异步与否,都没有任何意义。我知道解释器在开始执行下一行代码之前不会等待任何同步进程完成。但是同步语句不是按照它们在代码中出现的顺序完全依次处理,而异步语句不是按照它们在代码中出现的顺序开始处理吗?这样,如果异步过程很快出错,则可能会错过事件事件处理程序没有提前声明?事件处理程序不像函数声明那样被提升,不是吗?这是我仍然觉得困惑的部分。
在Jake Archibald 撰写的关于 Promise 的文章中,他在介绍中提供了一个有关图像加载的示例并写道:
不幸的是,在上面的示例中,事件可能发生在我们开始监听事件之前,因此我们需要使用图像的“完整”属性来解决这个问题。
和
在我们有机会收听之前,这不会捕获错误的图像;不幸的是 DOM 没有给我们一个方法来做到这一点。另外,这是加载一个图像,如果我们想知道一组图像何时加载,事情会变得更加复杂。
这给人的印象是顺序很重要,因此,在图像的情况下,如果可能,应在声明所有事件处理程序后分配源,以便不错过听到的事件。对我来说重要的部分是事件可以在事件处理程序被声明/注册之前发生。
我尝试遵循在 indexedDB 中声明事件处理程序后发出请求的相同模式,但这似乎不可能,因为在发出请求之前没有任何内容可以附加事件。
即使所有语句都是异步的,例如MDN Web 文档中有关使用 IndexedDB 的示例,有些事情仍然相当令人困惑。这objectStore.transaction.oncomplete是一个有趣的说法。我们正在等待 objectStore 创建,然后再尝试向其写入数据。(我认为在 onupgradeneeded 事件中写入数据被认为是不好的做法;因此,我们不使用该语句。)但令人困惑的是为什么我们不担心在其中创建索引之前创建 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);
});
};
};
Run Code Online (Sandbox Code Playgroud)
对答案/评论的澄清/回应
感谢您花时间回答我的问题并补充解释。
首先,我所说的“之前”只是指语句在脚本中出现的顺序。
我想我遵循你的类比,这是一个很好的类比。我只是仍然不清楚为什么员工直到第二天才将工作提交给秘书,因为可以保证秘书会在那里接收工作。
这听起来类似于这样一个事实:JavaScript 解释器在执行相当于编译脚本的操作时,会提升函数声明,以便可以在函数声明完成之前在代码中调用函数。
看来你的说法,用我简单的话来说,JS 引擎在最终执行之前的某个时刻,会分配事件处理程序(秘书)在比请求(员工)更早的纪元中注册。 )最终触发事件将完成。因此,请求语句相对于事件处理程序出现在代码中的哪个位置并不重要,也就是说,只要它们是在同一纪元内定义的即可。
JS 引擎不知道请求何时完成,只知道事件处理程序何时已注册以开始侦听以及请求何时开始。只要 JS 引擎有一个进程可以正确地排序这些步骤,而与语句在代码中出现的顺序无关,这样就不会错过事件,那么这对我来说与函数声明的提升没有什么不同,而且我不认为真的必须再考虑很多才能完成我的任务。
然而,我仍然想更好地理解什么是纪元,至少知道这些陈述是在同一个纪元内做出的。我在 MDN Web Docs 的“Concurrecny Model and Event Loop”文章中没有看到任何关于纪元的提及。您介意向我指出您知道的任何好资源吗?
谢谢。
最后的注释
我通过堆栈溢出上的链接发现了这两个项目。八年前有人问过同样的问题,并以相同的方式回答了这个问题,但使用了不同的术语;也就是说,JavaScript 代码将“运行至完成”或具有运行至完成语义,而不是纪元。这个问题让您参考这个文档,可以在该文档中搜索“运行到完成”,以阅读关于为什么在注册事件处理程序之前发出请求的设置中不存在竞争条件的两个交流。我有一本 David Flanagan 写的旧 JavaScript 书,在讨论 JS“程序”的执行时指出,因为 JS 具有单线程执行,所以永远不必担心竞争条件;但我不知道他是否指的是这种情况。
因此,这个问题在过去已经被问过和回答过很多次了,我想我只是另一个新手问一个老问题,就好像我是第一个想到它的人一样,并且对 JS 如何处理没有足够的了解。
上面链接的文章“并发模型和事件循环”有一个简短的“运行到完成”部分;但直到阅读上面链接的最后一个文档后我才理解其含义。
我现在认为它的意思是函数中的所有代码将在任何其他代码开始之前运行完成,这似乎有两种解释。
其一是,当函数代码中到达该语句时,对数据库的异步请求将排队,但直到函数中的所有其他语句(包括随后声明的事件处理程序)运行后才会真正开始。
或者,根据上面最后一个链接的文档,异步请求可能会在事件处理程序注册之前运行甚至完成,但其完成的通知将保留在队列中,直到事件处理程序中的其余语句之后才会执行。函数已运行并且事件处理程序已注册。
解释 2 似乎是准确的,但无论实际情况如何,它现在对我来说都足够有意义,并解释了为什么秘书总是在员工提交工作之前就在那里,以及为什么即使员工完成了工作不到一纳秒,员工就不会提交工作,直到第二天秘书肯定会在场接收工作。员工可以将工作完成通知放入队列中,但队列直到第二天才会发出通知让秘书听到。
感谢乔什(Josh)对纪元的含义以及该术语如何在操作中发挥作用的额外解释。我接受了您的回答,并感谢您花时间将其全部写出来。
现在我似乎明白为什么事件处理程序声明可以在代码中比请求的发出晚一些,我仍然不明白为什么我们可以创建一个对象存储,然后立即在该对象存储上创建一个索引,而无需必须等到我们知道对象存储已成功创建,除非它是同步的或在 versionchange 事务/ onupgradeneeded 事件中发生其他特殊情况。MDN Web Docs 的 createObjectStore 描述中没有提到任何事件,也没有包含任何侦听器的示例;所以; 我只是假设它永远没有必要。
再次感谢。
为什么在定义事件处理程序之前发出请求?
不要紧。
例如,
request = objectStore.add(data)在声明 request.onsuccess 和 request.onerror 函数之前进行。它是否正确?
是的,这是正确的,因为这并不重要。
我会小心你之前使用这个词的情况。也许它对我的意义和对你的意义不同。我不知道。但也许这就是让你绊倒的原因。
请求是否有可能在事件处理程序注册之前完成?
如果您在发出请求时在同一时期注册事件处理程序,则不会。该请求仅在稍后的时期完成。
好的,这是我尝试通过示例进行解释(抱歉,如果这不好!)。拟人化通常是一种很好的教育技巧,并且比使用原始技术术语更不那么令人生畏,所以让我们继续吧。
假设您是老板,有员工。假设您要求一名员工为您做一些工作。然后,您要求该员工在完成工作后向您的秘书报告。在要求员工去做其他工作后,你立即继续做自己的工作,而不需要等待该员工完成工作并报告。你们基本上是同时做工作的。
现在,在这种情况下,如果您在向员工提出做某事的请求时没有秘书,会发生什么情况?好吧,没问题。在该员工完成工作之前,甚至在该员工知道要向谁汇报之前,您就去聘请另一位秘书,这很好,因为员工只知道他们向您的秘书汇报。员工在被分配工作时不知道你的秘书是否存在,也不需要知道。失踪的秘书并没有阻止该员工开始工作或了解要完成的工作。当员工完成工作时,您就有一位秘书准备就绪并等待着。或者,你不这样做,因为你碰巧甚至不关心工作是否真正完成,你只是发出命令并信任员工完成他们的工作,无论如何。如果你需要做一些其他工作,而这些工作必须等到第一个项目完成后,你真的只关心让他们向你的秘书汇报,这是一个不同的问题。
假设您在向员工分配工作时已经有一名秘书。这种已经有一名秘书的情况与在分配工作后不久但尚未完成之前就去雇用一名秘书的情况有什么区别?没有区别。
现在,让我们尝试真正解决您的担忧。您的建议是,在您知道员工是否完成任务之前,似乎不可能可靠地聘请该秘书。我认为这是一个严重的误解。这样做是完全有可能的。这是为什么?我想这不是最容易掌握的事情。
我将稍微延伸一下这个比喻并施加一个奇怪的规则。无论您交给员工的项目多么简单,即使只是早上跑去给您送咖啡,他们也永远不会在当天回复您。他们总是会在晚些时候完成工作,最早是明天。他们甚至可能在您告诉他们后的一瞬间完成工作,但他们永远不会立即回复您或您的秘书,他们总是最早会延迟到明天。
这意味着您有一整天的时间去聘请在您向员工下达命令时并不存在的秘书。只要你在明天之前做到这一点,就没有问题。当员工明天回复时,该秘书将存在并为您工作,并且能够接收来自员工的消息。
编辑对您添加的评论的回复:
是的,提升在很多方面都是相似的。事情发生的顺序可能与用代码编写的顺序不同。提升当然是同步的,所以不是完全相似,但乱序方面仍然相似。
Epoch 只是我自己用的词,用于事件循环的单次迭代。就像使用 i for i 从 0 到 2 的 for 循环一样,有 3 个纪元:迭代 0、迭代 1 和迭代 2。我只是将它们称为纪元,因为它就像时间类别。
在有希望的情况下,它甚至可能是一个微任务。在 js worker 的情况下,它可能是类似线程的(workers 是旧的子 iframe 技术的新热点)。基本上,这些都是“实现”一次做多件事的方法。Node 将其称为“tick”,并且具有类似的功能,可以将nextTick()代码执行推迟到其循环的下一个“tick”。在单个纪元内,事情按照它们被写入的顺序发生(值得注意的是,提升全部发生在纪元 0 中)。但有些代码可能是异步的,因此会跨时期发生,因此可能会以与编写时不同的顺序运行。较早编写的代码可能会在较晚的时期发生。
当你提出请求时,它会说,开始做这件事,并在下一个纪元最早回复我。在当前纪元结束之前,您必须为请求注册处理程序。
某些代码(例如示例中提到的图像预加载器的情况)必须考虑到它太晚附加侦听器(图像正在备用时间轴中预加载,有些可能已经加载,在某些浏览器中这意味着load 不会触发),因此它想要检查 imageElement.complete 以捕获这种情况。在事件侦听器实现的其他情况下,某些调度程序实现将向新添加的侦听器触发事件,以处理已经发生的事件,而新侦听器在事件发生时未侦听。但这并不是事件侦听器实现的普遍特征,而只是某些实现的特征。
对于来自 onupgradeneeded 内部的 transaction.oncomplete 的情况,这并不是一个很好的例子。它正在做它不需要做的事情。