【发布时间】:2018-07-18 05:48:42
【问题描述】:
感觉这一定是个愚蠢的问题,但我不明白在 indexedDB 中发出请求的基本原理。
为什么在定义事件处理程序之前发出请求?例如,request = objectStore.add(data) 在声明 request.onsuccess 和 request.onerror 函数之前创建。它是否正确?是否有可能在注册事件处理程序之前完成请求?
我将它与创建图像元素进行比较,然后声明 onload 和 onerror 的事件处理程序,所有这些都是在将 source 属性设置为文件的位置并尝试加载它之前。但是在发出请求之前不能创建请求“元素”;因此,在发出请求之前,没有任何东西可以附加事件。
请让我知道我在这里缺少什么。我一直在毫无问题地从 indexedDB 写入和检索数据,并且认为我的编码正确;但我想确保这是正确的并且永远有效。
谢谢。
重复响应
我前段时间读过这个问题和答案,当我第一次开始阅读 indexedDB 时,我完全忘记了它。如果我在写这个问题之前再次找到它,我可能不会提交它并且会接受代码应该可以解决我是否理解它。处理错误事件和事务中止是让我再次思考语句顺序的原因。
但是,在再次阅读答案后,除了接受它并希望它永远有效之外,我还没有进一步了解。我不是要冷嘲热讽。从某种意义上说,我对事件循环和纪元的思考能力有限,而且所有事情都是同时发生的,这让我感到困惑。
在 epoch 结束(或下一个开始,无论您认为更容易理解什么),底层 JS 引擎返回并查看注册执行的内容,然后几乎同时执行所有内容。
必须有执行顺序,否则无论异步与否,都没有任何意义。我知道解释器在开始执行下一行代码之前不会等待任何同步过程完成。但是同步语句不是按照它们在代码中出现的顺序依次处理完的,而异步语句不是按照它们在代码中出现的顺序开始的,这样如果异步过程很快出错,如果事件处理程序没有提前声明?事件处理程序不像函数声明那样被提升,是吗?这是我仍然感到困惑的部分。
在这篇由 Jake Archibald 撰写的关于 Promise 的 article 中,他在介绍中展示了一个关于图像加载的示例并写道:
不幸的是,在上面的示例中,事件可能在我们开始监听它们之前就发生了,因此我们需要使用图像的“完整”属性来解决这个问题。
和
这不会捕获在我们有机会收听之前出错的图像;不幸的是,DOM 并没有给我们一种方法来做到这一点。此外,这是加载一个图像,如果我们想知道一组图像何时加载,事情会变得更加复杂。
这给人的印象是顺序很重要,因此,在图像的情况下,如果可能,应在声明所有事件处理程序后分配源,以免错过听力事件。对我来说重要的部分是事件可以在事件处理程序被声明/注册之前发生。
在 indexedDB 中声明事件处理程序后,我尝试按照相同的模式发出请求,但这似乎是不可能的,因为在发出请求之前没有任何事件可以附加到。
即使所有语句都是异步的,例如 MDN Web Docs on Using IndexedDB 中的this 示例,有些事情仍然相当混乱。 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);
});
};
};
对答案/评论的澄清/回应
感谢您抽出宝贵时间回答我的问题并提供补充说明。
首先,“之前”是指语句在脚本中出现的顺序。
我认为我遵循您的类比,这是一个很好的类比。我只是仍然不清楚为什么员工直到第二天才能将他们的工作提交给秘书,因为可以保证秘书会在那里接收它。
这听起来类似于 javascript 解释器在执行相当于编译脚本时会提升函数声明,这样就可以在函数声明之前在代码中调用函数。
看起来你的说法,用我的简单术语重申,JS 引擎在最终执行之前的某个时刻,分配事件处理程序(秘书)在比请求更早的时期注册最终触发事件的(员工)将完成。因此,请求语句相对于事件处理程序出现在代码中的哪个位置并不重要,也就是说,只要它们是在同一时期内定义的。
JS 引擎不知道请求何时完成,但只知道事件处理程序已注册开始侦听以及请求何时开始。只要 JS 引擎有一个过程来正确地对这些步骤进行排序,而独立于语句在代码中出现的顺序,这样就不会错过一个事件,那么对我来说,这与函数声明的提升没有什么不同,我不为了完成我的任务,真的必须再多考虑一下了。
但是,我仍然想更好地理解什么是纪元,至少在知道声明是在同一个纪元内作出的方面。我在 MDN Web Docs 中关于“并发模型和事件循环”的文章中没有看到任何时代的提及。您介意向我指出您知道的任何好的资源吗?
谢谢。
最后说明
我通过一个链接发现了这两个项目,这里是堆栈溢出。八年前提出了同样的问题,并以相同的方式回答了不同的术语。也就是说,javascript 代码将“运行至完成”或具有运行至完成语义,而不是 epoch。这个question 向您推荐这个document,可以搜索“运行到完成”来阅读两个关于为什么在注册事件处理程序之前发出请求的设置中没有竞争条件的交流。 David Flanagan 写的较早的 JavaScript 书在讨论 JS“程序”的执行时指出,由于 JS 具有单线程执行,因此不必担心竞争条件。但我不知道他是不是指的正是这种情况。
因此,这个问题在过去已经被多次询问和回答,我想我只是另一个新手问一个老问题,就好像我是第一个想到它的人一样,并且对 JS 的处理方式没有足够的了解.
上面链接的文章“并发模型和事件循环”有一个简短的“运行到完成”部分;但直到阅读了上面链接的最后一个文档后,我才明白它的含义。
我现在认为它的意思是一个函数中的所有代码将在任何其他代码开始之前运行到完成,这似乎有两种解释。
一个是在函数代码中到达语句时对数据库的异步请求排队,但直到函数中的所有其他语句都运行后才真正开始,包括声明的事件处理程序之后。
或者,根据上面最后一个链接文档,异步请求可能会在事件处理程序注册之前运行甚至完成,但它的完成通知将保留在队列中,直到在函数中的其余语句都已运行,并且事件处理程序已注册。
解释 2 似乎是准确的,但无论哪种情况,现在对我来说这一切都足够合理,并解释了为什么秘书会在员工提交工作之前一直在场,以及为什么,即使员工在一纳秒内完成工作,员工直到第二天保证有秘书在场接受它时才会提交工作。员工可以将工作完成通知放入队列中,但队列直到第二天才会发出通知让秘书听到。
感谢 Josh 对时代的含义以及该术语如何在操作中发挥作用的额外解释。我接受了您的回答,并感谢您抽出时间将其全部写出来。
现在我似乎明白了为什么事件处理程序声明可以在代码中比发出请求更晚进行,但我仍然不明白为什么我们可以创建一个对象存储然后立即在其上创建一个索引对象存储,而不必等到我们知道对象存储已成功创建,除非它是同步的或在 versionchange 事务/onupgradeneeded 事件中发生其他特殊情况。在 MDN Web Docs 的 createObjectStore 描述中没有提到任何事件,也没有在其上具有任何侦听器的示例;所以;我只是假设它从来没有必要。
再次感谢。
【问题讨论】:
-
我不确定要在我以前的答案中添加什么。也许试着问一个更狭窄、更清晰、更具体的问题?我将是第一个承认从编写同步代码到异步代码的飞跃确实令人困惑的人。
-
平心而论,img 的例子是一个不幸的例子。 Img onload 也应该异步触发;即即使您确实设置了 onload 处理程序,但您确实设置了它的 src,但仍然以同步方式,您的事件应该触发。这是某些实现中的错误,可能是因为可以通过多种方式设置此 src(例如直接从标记中)。
标签: javascript indexeddb