IndexedDB事务和承诺之间不一致的相互作用

时间:2021-07-29 11:07:34

I saw sync-promise posted on Reddit and got into a discussion with the author. We noticed some weird inconsistencies in the relationship between IndexedDB transactions and promises.

我看到sync-promise被发布在Reddit上,并和作者进行了讨论。我们注意到IndexedDB事务和承诺之间存在一些奇怪的不一致性。

IndexedDB transactions auto-commit when all the onsuccess events finish. One complication is that you can't do anything asynchronous inside an onsuccess callback except do another operation on the same transaction. For example, you can't start an AJAX request in an onsuccess and then reuse the same transaction after the AJAX request returns some data.

当所有的onsuccess事件完成时,IndexedDB事务自动提交。一个复杂的问题是,除了在同一事务上执行另一个操作之外,在onsuccess回调中不能做任何异步操作。例如,不能在onsuccess中启动AJAX请求,然后在AJAX请求返回一些数据后重用相同的事务。

What do promises have to do with it? As I understand it, promise resolution is supposed to always be asynchronous. This would imply that you can't use promises without auto-committing an IndexedDB transaction.

承诺和它有什么关系?按照我的理解,承诺解析应该总是异步的。这意味着,如果不自动提交IndexedDB事务,就不能使用承诺。

Here is an example of what I'm talking about:

这里有一个例子说明我所说的:

var openRequest = indexedDB.open("library");

openRequest.onupgradeneeded = function() {
  // The database did not previously exist, so create object stores and indexes.
  var db = openRequest.result;
  var store = db.createObjectStore("books", {keyPath: "isbn"});
  var titleIndex = store.createIndex("by_title", "title", {unique: true});
  var authorIndex = store.createIndex("by_author", "author");

  // Populate with initial data.
  store.put({title: "Quarry Memories", author: "Fred", isbn: 123456});
  store.put({title: "Water Buffaloes", author: "Fred", isbn: 234567});
  store.put({title: "Bedrock Nights", author: "Barney", isbn: 345678});
};

function getByTitle(tx, title) {
  return new Promise(function(resolve, reject) {
    var store = tx.objectStore("books");
    var index = store.index("by_title");
    var request = index.get("Bedrock Nights");
    request.onsuccess = function() {
      var matching = request.result;
      if (matching !== undefined) {
        // A match was found.
        resolve(matching);
      } else {
        // No match was found.
        console.log('no match found');
      }
    };
  });
}

openRequest.onsuccess = function() {
  var db = openRequest.result;
  var tx = db.transaction("books", "readonly");
  getByTitle(tx, "Bedrock Nights").then(function(book) {
    console.log('First book', book.isbn, book.title, book.author);
    return getByTitle(tx, "Quarry Memories");
  }).then(function(book) {
    console.log('Second book', book.isbn, book.title, book.author);
    // With native promises this gives the error:
    // InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
    // With bluebird everything is fine
  });
};

(Full disclosure: demo was created by paldepind, not me!)

(完全披露:演示是由paldepind创建的,而不是我!)

I've tried it in Chrome and Firefox. It fails in Firefox due to the transaction auto-committing, but it actually works in Chrome! Which behavior is correct? And if Firefox's behavior is correct, is it literally impossible to use "correct" promise implementations with IndexedDB transactions?

我在Chrome和火狐上试过。由于事务自动提交,它在Firefox中失败了,但是它在Chrome中确实有效!哪些行为是正确的?如果Firefox的行为是正确的,那么在IndexedDB事务中使用“正确”的承诺实现真的是不可能的吗?

Another complication: If I load bluebird before running the above demo, it works in both Chrome and Firefox. Does this imply that bluebird is resolving promises synchronously? I thought it wasn't supposed to do that!

另一个问题是:如果我在运行上面的演示之前加载bluebird,它可以在Chrome和Firefox中工作。这是否意味着蓝鸟正在同步地解决承诺?我以为它不应该那样做!

JSFiddle

JSFiddle

3 个解决方案

#1


8  

This is probably due to the difference between microtasks and tasks ("macrotasks"). Firefox has never had a standards-complaint promise implementation that uses microtasks, whereas Chrome, Bluebird, and others correctly use microtasks. You can see this in how a microtask (which executes "sooner" than a macrotask, but still async) falls inside the transaction boundary, whereas a macrotask (e.g. from Firefox's promises) does not.

这可能是由于微任务和任务(“宏任务”)之间的差异造成的。Firefox从未有过使用微任务的标准抱怨实现,而Chrome、Bluebird和其他浏览器则正确地使用微任务。您可以从微任务(比宏任务执行得“更快”,但仍然是异步的)在事务边界内看到这一点,而宏任务(例如来自Firefox的承诺)则不是这样。

So, this is a Firefox bug.

这是Firefox的一个bug。

#2


6  

Ok, so I've once again taken a deep dive into the IndexedDB, the DOM and the HTML specification. I really need to get this right for SyncedDB since it relies heavily on promises inside transactions.

好的,我再次深入研究了IndexedDB、DOM和HTML规范。我确实需要对SyncedDB进行正确处理,因为它严重依赖于事务内部的承诺。

The crux of the problem is whether or not the delayed execution of the onFulfilled and the onRejected callbacks to then that Promises/A+ compliant must exhibit will trigger an IndexedDB transaction commit.

问题的关键在于,oncompleted和当时答应/A+兼容的onreject回调的延迟执行是否会触发IndexedDB事务提交。

The IndexedDB rules for a transactions lifetime are actually pretty straight forward when you extract them from the specification and line them up:

当您从规范中提取它们并将它们排列起来时,事务生命周期的IndexedDB规则实际上非常直接:

  • Request can only made against a transaction when its active flag is set to true (as specified here).
  • 请求只能在事务的活动标志被设置为true(如此处所指定)时针对事务发出。
  • When a transaction is created it is initially active until control is returned to the the browsers event loop (this is specified in the transaction creation steps).
  • 当创建一个事务时,它最初是活动的,直到将控件返回给浏览器事件循环(这在事务创建步骤中指定)。
  • Every time a success or error event is fired, the transactions active flag is set to true before as the last step before the event is dispatched. After the event dispatch the transaction is flagged as inactive again (this is specified in the steps for firing a success/error event.
  • 每次触发成功或错误事件时,事务活动标志被设置为true,作为事件发送之前的最后一步。事件分派之后,事务再次被标记为非活动(这在触发成功/错误事件的步骤中指定)。
  • When a transaction can no longer become active it will automatically be committed (as specified here).
  • 当事务不能再激活时,它将自动提交(此处指定)。

This roughly translates to:

这大约相当于:

  • When you create a transaction you can place as many request against it as you wish.
  • 当您创建一个事务时,您可以针对它放置任意数量的请求。
  • From then on new request can only be made inside event handlers for another requests success or error event listener.
  • 从那时起,对于新的请求,只能在事件处理程序内部为另一个请求成功或错误事件侦听器进行处理。
  • When all requests has been executed and no new requests placed the transaction will commit.
  • 当所有请求被执行,并且没有新的请求被放置时,事务将提交。

The question then becomes: If a promise is fulfilled inside a request's success or error event listener will its onFulfilled callbacks be invoked before the IndexedDB sets the transaction as inactive again? I.e. will onFullfilled callbacks be called as part of step 3 in firing a success event?

接下来的问题是:如果一个承诺在请求的成功或错误事件侦听器中实现,那么在IndexedDB将事务设置为非活动之前,它的oncompleted回调将被调用吗?例如,在触发成功事件的步骤3中,是否会调用onFullfilled回调?

The step dispatches an event and IndexedDB uses DOM events so the actual operation performed is beyond the IndexedDB specification. The steps for dispatching an event is, instead, specified here in the DOM specification. Going over the steps it becomes clear that at no point is a microtask (which would call the promise callbacks) checkpoint performed. So the initial conclusion is that the transaction will be closed before any onFulfilled callbacks will be invoked.

该步骤分派一个事件,IndexedDB使用DOM事件,因此实际执行的操作超出了IndexedDB规范。发送事件的步骤是在DOM规范中指定的。通过这些步骤可以清楚地看到,在任何时候都没有执行微任务(它将调用promise回调)检查点。因此,最初的结论是,在调用任何已完成的回调之前,事务将被关闭。

However, if we attach the event listeners by specifying an onsuccess attribute on the request object things gets more hairy. In that case we are not simply adding an event listener as per the DOM specification. We are instead setting an event handler IDL attribute as defined in the HTML specification.

但是,如果我们通过在请求对象上指定onsuccess属性来附加事件监听器,事情会变得更加复杂。在这种情况下,我们不只是按照DOM规范添加事件侦听器。相反,我们正在设置HTML规范中定义的事件处理程序IDL属性。

When we do that the callback is not added directly to the list of event listeners. It is instead "wrapped" inside the the event handlers processing algorithm. This algorithm performs the following important operations:

当我们这样做时,回调不会直接添加到事件侦听器列表中。而是在事件处理程序处理算法中“包装”。该算法执行以下重要操作:

  1. In step 3 it runs the jump to code entry-point algorithm.
  2. 在步骤3中,它运行跳转到代码入口点算法。
  3. This then performs the steps to clean up after running a callback which
  4. 然后在运行回调之后执行清理步骤
  5. Finally, this performs a microtask checkpoint. Which means that your promise callbacks will be invoked before the transaction is marked as inactive! Hurrah!
  6. 最后,执行一个微任务检查点。这意味着您的承诺回调将在事务被标记为非活动之前被调用!好哇!

This is good news! But it is weird how the answer depends on whether or not you listen for the success event by using addEventListener or set a onsuccess event handler. If you do the former the transaction should be inactive when your promise's onFulfilled callbacks is invoked and if you do the later it should still be active.

这是一个好消息!但奇怪的是,答案取决于您是否通过使用addEventListener侦听成功事件或设置onsuccess事件处理程序侦听成功事件。如果您执行前一个事务,那么当您的承诺的已完成回调被调用时,事务应该是不活动的,如果您稍后执行,它仍然应该是活动的。

I was, however not able to reproduce the difference in existing browsers. With native promises Firefox fails at the example code no matter what and Chrome succeeds even when using addEventListener. It is possible that I've overlooked or misunderstood something in the specifications.

然而,我无法再现现有浏览器的差异。使用本机承诺,不管发生什么,Firefox在示例代码上都会失败,而Chrome在使用addEventListener时也会成功。可能我忽略或误解了规范中的某些内容。

As a final note Bluebird promises will close transactions in Internet Explorer 11. This is due to the scheduling that Bluebird uses in IE. My synchronized promise implementation works inside transactions in IE.

最后,蓝鸟承诺将在Internet Explorer 11中完成交易。这是由于蓝鸟在IE中使用的调度。我的同步承诺实现在IE的事务中工作。

#3


4  

You are correct: Promises are resolved asynchronously, and IndexedDB has some synchronous requirements. While other answers point out that native promises may work correctly with IndexedDB in certain versions of certain browsers, it is likely as a practical matter that you will have to deal with the issue of it not working in some of the browsers you're targeting.

您是正确的:承诺是异步解决的,而IndexedDB有一些同步需求。虽然其他的答案指出,在某些浏览器的特定版本中,本地承诺可能会正确地使用IndexedDB,但它很可能是一个实际问题,您将不得不处理它不在您的目标浏览器中工作的问题。

Using a synchronous promise implementation instead, however, is a horrible idea. Promises are asynchronous for very good reasons, and you are introducing needless chaos and potential for bugs if you make them synchronous instead.

然而,使用同步承诺实现却是一个可怕的想法。由于非常好的原因,承诺是异步的,如果您让它们同步,那么您将引入不必要的混乱和潜在的bug。

There is a fairly straightforward workaround, however: use a Promise library that provides a way to explicitly flush its callback queue, and an IndexedDB wrapper that flushes the promise callback queue after invoking event callbacks.

不过,这里有一个相当简单的解决方案:使用承诺库(提供显式刷新回调队列的方法)和IndexedDB包装器(在调用事件回调之后刷新承诺回调队列)。

From the Promises/A+ point of view, there isn't any difference between the handlers being called at the end of the event, or at the beginning of the next tick cycle -- they are still being called after all the code that set up the callbacks has finished, which is the important part of Promise asynchrony.

从承诺/ A +的观点来看,没有任何区别处理程序被调用的事件,或明年初的蜱虫循环——他们仍然被称为毕竟设置回调函数完成的代码,即保证异步的重要组成部分。

This allows you to use promises that are asynchronous, in the sense of meeting all the Promises/A+ guarantees, but which still ensure that the IndexedDB transaction isn't closed. So you still get all the benefits of callbacks not happening "all at once".

这允许您使用异步的承诺,即满足所有的承诺/A+保证,但仍然确保IndexedDB事务没有关闭。因此,您仍然可以获得回调的所有好处,而不是“全部同时发生”。

The catch of course is that you need libraries that support this, and not every Promise implementation exposes a way to specify a scheduler or to flush its callback queue. Likewise, I'm not aware of any open source IndexedDB wrappers that have support for this.

当然,您需要支持此功能的库,并且不是每个承诺实现都公开指定调度程序或刷新其回调队列的方法。同样,我也不知道有任何开源的IndexedDB包装器支持这一点。

If you are writing your own IndexedDB wrapper with Promsies, though, it would be good for it to use an appropriate Promise implementation, and flush its callback queue accordingly. One easy option would be to embed one of the many "micropromise" implementations that are only 100 lines or so of Javascript, and modify it as needed. Alternately, using one of the larger mainstream Promise libraries with custom scheduling support would be doable.

如果您正在用Promsies编写自己的IndexedDB包装,那么最好使用适当的Promise实现,并相应地刷新回调队列。一个简单的选择是嵌入许多“微承诺”实现之一,这些实现只有大约100行Javascript代码,并根据需要对其进行修改。或者,使用一个较大的主流承诺库并提供自定义调度支持也是可行的。

Do not use a synchronous promise library, the synchronous Bluebird build, or a synchronous scheduler. If you do that, you might as well abandon promises altogether and use straight callbacks.

不要使用同步承诺库、同步蓝鸟构建或同步调度程序。如果您这样做,您还不如完全放弃承诺,直接回调。

Follow-up note: one commenter suggests that a synchronous promise is as safe as flushing a callback queue. But they are wrong. Horribly, horribly wrong. You can reason about a single event handler well enough to say "there isn't any other code running here; it's okay to invoke the callbacks now". To make a similar analysis with synchronous promises requires a complete understanding of how everything calls everything else... which is precisely opposite from the reason you want promises in the first place.

后续注意:一个评论建议同步承诺和刷新回调队列一样安全。但他们错了。可怕,可怕的是错误的。您可以很好地对单个事件处理程序进行推理,并说“这里没有运行任何其他代码;现在可以调用回调”。要对同步承诺进行类似的分析,需要对所有事物如何调用其他事物有完整的理解……这和你想要承诺的原因恰恰相反。

In the specific sync-promise implementation, the sync-promise author claims that their promise library is now "safe", and doesn't "release Zalgo". They are once again wrong: it isn't safe, and does release Zalgo. The author apparently did not actually understand the article about "releasing Zalgo", and has successfully reimplemented jQuery promises, which are widely considered horribly broken for a number of reasons, including their Zalgo-ness.

在特定的synpromise实现中,synpromise作者声称他们的promise库现在是“安全的”,而不是“发布Zalgo”。他们又错了:它不安全,而且释放了Zalgo。作者显然没有真正理解关于“释放Zalgo”的文章,并且成功地重新实现了jQuery的承诺,由于许多原因,这些承诺被广泛认为是可怕的违背,包括它们的Zalgo性。

Synchronous promises are simply not safe, no matter your implementation.

同步承诺根本不安全,无论您的实现如何。

#1


8  

This is probably due to the difference between microtasks and tasks ("macrotasks"). Firefox has never had a standards-complaint promise implementation that uses microtasks, whereas Chrome, Bluebird, and others correctly use microtasks. You can see this in how a microtask (which executes "sooner" than a macrotask, but still async) falls inside the transaction boundary, whereas a macrotask (e.g. from Firefox's promises) does not.

这可能是由于微任务和任务(“宏任务”)之间的差异造成的。Firefox从未有过使用微任务的标准抱怨实现,而Chrome、Bluebird和其他浏览器则正确地使用微任务。您可以从微任务(比宏任务执行得“更快”,但仍然是异步的)在事务边界内看到这一点,而宏任务(例如来自Firefox的承诺)则不是这样。

So, this is a Firefox bug.

这是Firefox的一个bug。

#2


6  

Ok, so I've once again taken a deep dive into the IndexedDB, the DOM and the HTML specification. I really need to get this right for SyncedDB since it relies heavily on promises inside transactions.

好的,我再次深入研究了IndexedDB、DOM和HTML规范。我确实需要对SyncedDB进行正确处理,因为它严重依赖于事务内部的承诺。

The crux of the problem is whether or not the delayed execution of the onFulfilled and the onRejected callbacks to then that Promises/A+ compliant must exhibit will trigger an IndexedDB transaction commit.

问题的关键在于,oncompleted和当时答应/A+兼容的onreject回调的延迟执行是否会触发IndexedDB事务提交。

The IndexedDB rules for a transactions lifetime are actually pretty straight forward when you extract them from the specification and line them up:

当您从规范中提取它们并将它们排列起来时,事务生命周期的IndexedDB规则实际上非常直接:

  • Request can only made against a transaction when its active flag is set to true (as specified here).
  • 请求只能在事务的活动标志被设置为true(如此处所指定)时针对事务发出。
  • When a transaction is created it is initially active until control is returned to the the browsers event loop (this is specified in the transaction creation steps).
  • 当创建一个事务时,它最初是活动的,直到将控件返回给浏览器事件循环(这在事务创建步骤中指定)。
  • Every time a success or error event is fired, the transactions active flag is set to true before as the last step before the event is dispatched. After the event dispatch the transaction is flagged as inactive again (this is specified in the steps for firing a success/error event.
  • 每次触发成功或错误事件时,事务活动标志被设置为true,作为事件发送之前的最后一步。事件分派之后,事务再次被标记为非活动(这在触发成功/错误事件的步骤中指定)。
  • When a transaction can no longer become active it will automatically be committed (as specified here).
  • 当事务不能再激活时,它将自动提交(此处指定)。

This roughly translates to:

这大约相当于:

  • When you create a transaction you can place as many request against it as you wish.
  • 当您创建一个事务时,您可以针对它放置任意数量的请求。
  • From then on new request can only be made inside event handlers for another requests success or error event listener.
  • 从那时起,对于新的请求,只能在事件处理程序内部为另一个请求成功或错误事件侦听器进行处理。
  • When all requests has been executed and no new requests placed the transaction will commit.
  • 当所有请求被执行,并且没有新的请求被放置时,事务将提交。

The question then becomes: If a promise is fulfilled inside a request's success or error event listener will its onFulfilled callbacks be invoked before the IndexedDB sets the transaction as inactive again? I.e. will onFullfilled callbacks be called as part of step 3 in firing a success event?

接下来的问题是:如果一个承诺在请求的成功或错误事件侦听器中实现,那么在IndexedDB将事务设置为非活动之前,它的oncompleted回调将被调用吗?例如,在触发成功事件的步骤3中,是否会调用onFullfilled回调?

The step dispatches an event and IndexedDB uses DOM events so the actual operation performed is beyond the IndexedDB specification. The steps for dispatching an event is, instead, specified here in the DOM specification. Going over the steps it becomes clear that at no point is a microtask (which would call the promise callbacks) checkpoint performed. So the initial conclusion is that the transaction will be closed before any onFulfilled callbacks will be invoked.

该步骤分派一个事件,IndexedDB使用DOM事件,因此实际执行的操作超出了IndexedDB规范。发送事件的步骤是在DOM规范中指定的。通过这些步骤可以清楚地看到,在任何时候都没有执行微任务(它将调用promise回调)检查点。因此,最初的结论是,在调用任何已完成的回调之前,事务将被关闭。

However, if we attach the event listeners by specifying an onsuccess attribute on the request object things gets more hairy. In that case we are not simply adding an event listener as per the DOM specification. We are instead setting an event handler IDL attribute as defined in the HTML specification.

但是,如果我们通过在请求对象上指定onsuccess属性来附加事件监听器,事情会变得更加复杂。在这种情况下,我们不只是按照DOM规范添加事件侦听器。相反,我们正在设置HTML规范中定义的事件处理程序IDL属性。

When we do that the callback is not added directly to the list of event listeners. It is instead "wrapped" inside the the event handlers processing algorithm. This algorithm performs the following important operations:

当我们这样做时,回调不会直接添加到事件侦听器列表中。而是在事件处理程序处理算法中“包装”。该算法执行以下重要操作:

  1. In step 3 it runs the jump to code entry-point algorithm.
  2. 在步骤3中,它运行跳转到代码入口点算法。
  3. This then performs the steps to clean up after running a callback which
  4. 然后在运行回调之后执行清理步骤
  5. Finally, this performs a microtask checkpoint. Which means that your promise callbacks will be invoked before the transaction is marked as inactive! Hurrah!
  6. 最后,执行一个微任务检查点。这意味着您的承诺回调将在事务被标记为非活动之前被调用!好哇!

This is good news! But it is weird how the answer depends on whether or not you listen for the success event by using addEventListener or set a onsuccess event handler. If you do the former the transaction should be inactive when your promise's onFulfilled callbacks is invoked and if you do the later it should still be active.

这是一个好消息!但奇怪的是,答案取决于您是否通过使用addEventListener侦听成功事件或设置onsuccess事件处理程序侦听成功事件。如果您执行前一个事务,那么当您的承诺的已完成回调被调用时,事务应该是不活动的,如果您稍后执行,它仍然应该是活动的。

I was, however not able to reproduce the difference in existing browsers. With native promises Firefox fails at the example code no matter what and Chrome succeeds even when using addEventListener. It is possible that I've overlooked or misunderstood something in the specifications.

然而,我无法再现现有浏览器的差异。使用本机承诺,不管发生什么,Firefox在示例代码上都会失败,而Chrome在使用addEventListener时也会成功。可能我忽略或误解了规范中的某些内容。

As a final note Bluebird promises will close transactions in Internet Explorer 11. This is due to the scheduling that Bluebird uses in IE. My synchronized promise implementation works inside transactions in IE.

最后,蓝鸟承诺将在Internet Explorer 11中完成交易。这是由于蓝鸟在IE中使用的调度。我的同步承诺实现在IE的事务中工作。

#3


4  

You are correct: Promises are resolved asynchronously, and IndexedDB has some synchronous requirements. While other answers point out that native promises may work correctly with IndexedDB in certain versions of certain browsers, it is likely as a practical matter that you will have to deal with the issue of it not working in some of the browsers you're targeting.

您是正确的:承诺是异步解决的,而IndexedDB有一些同步需求。虽然其他的答案指出,在某些浏览器的特定版本中,本地承诺可能会正确地使用IndexedDB,但它很可能是一个实际问题,您将不得不处理它不在您的目标浏览器中工作的问题。

Using a synchronous promise implementation instead, however, is a horrible idea. Promises are asynchronous for very good reasons, and you are introducing needless chaos and potential for bugs if you make them synchronous instead.

然而,使用同步承诺实现却是一个可怕的想法。由于非常好的原因,承诺是异步的,如果您让它们同步,那么您将引入不必要的混乱和潜在的bug。

There is a fairly straightforward workaround, however: use a Promise library that provides a way to explicitly flush its callback queue, and an IndexedDB wrapper that flushes the promise callback queue after invoking event callbacks.

不过,这里有一个相当简单的解决方案:使用承诺库(提供显式刷新回调队列的方法)和IndexedDB包装器(在调用事件回调之后刷新承诺回调队列)。

From the Promises/A+ point of view, there isn't any difference between the handlers being called at the end of the event, or at the beginning of the next tick cycle -- they are still being called after all the code that set up the callbacks has finished, which is the important part of Promise asynchrony.

从承诺/ A +的观点来看,没有任何区别处理程序被调用的事件,或明年初的蜱虫循环——他们仍然被称为毕竟设置回调函数完成的代码,即保证异步的重要组成部分。

This allows you to use promises that are asynchronous, in the sense of meeting all the Promises/A+ guarantees, but which still ensure that the IndexedDB transaction isn't closed. So you still get all the benefits of callbacks not happening "all at once".

这允许您使用异步的承诺,即满足所有的承诺/A+保证,但仍然确保IndexedDB事务没有关闭。因此,您仍然可以获得回调的所有好处,而不是“全部同时发生”。

The catch of course is that you need libraries that support this, and not every Promise implementation exposes a way to specify a scheduler or to flush its callback queue. Likewise, I'm not aware of any open source IndexedDB wrappers that have support for this.

当然,您需要支持此功能的库,并且不是每个承诺实现都公开指定调度程序或刷新其回调队列的方法。同样,我也不知道有任何开源的IndexedDB包装器支持这一点。

If you are writing your own IndexedDB wrapper with Promsies, though, it would be good for it to use an appropriate Promise implementation, and flush its callback queue accordingly. One easy option would be to embed one of the many "micropromise" implementations that are only 100 lines or so of Javascript, and modify it as needed. Alternately, using one of the larger mainstream Promise libraries with custom scheduling support would be doable.

如果您正在用Promsies编写自己的IndexedDB包装,那么最好使用适当的Promise实现,并相应地刷新回调队列。一个简单的选择是嵌入许多“微承诺”实现之一,这些实现只有大约100行Javascript代码,并根据需要对其进行修改。或者,使用一个较大的主流承诺库并提供自定义调度支持也是可行的。

Do not use a synchronous promise library, the synchronous Bluebird build, or a synchronous scheduler. If you do that, you might as well abandon promises altogether and use straight callbacks.

不要使用同步承诺库、同步蓝鸟构建或同步调度程序。如果您这样做,您还不如完全放弃承诺,直接回调。

Follow-up note: one commenter suggests that a synchronous promise is as safe as flushing a callback queue. But they are wrong. Horribly, horribly wrong. You can reason about a single event handler well enough to say "there isn't any other code running here; it's okay to invoke the callbacks now". To make a similar analysis with synchronous promises requires a complete understanding of how everything calls everything else... which is precisely opposite from the reason you want promises in the first place.

后续注意:一个评论建议同步承诺和刷新回调队列一样安全。但他们错了。可怕,可怕的是错误的。您可以很好地对单个事件处理程序进行推理,并说“这里没有运行任何其他代码;现在可以调用回调”。要对同步承诺进行类似的分析,需要对所有事物如何调用其他事物有完整的理解……这和你想要承诺的原因恰恰相反。

In the specific sync-promise implementation, the sync-promise author claims that their promise library is now "safe", and doesn't "release Zalgo". They are once again wrong: it isn't safe, and does release Zalgo. The author apparently did not actually understand the article about "releasing Zalgo", and has successfully reimplemented jQuery promises, which are widely considered horribly broken for a number of reasons, including their Zalgo-ness.

在特定的synpromise实现中,synpromise作者声称他们的promise库现在是“安全的”,而不是“发布Zalgo”。他们又错了:它不安全,而且释放了Zalgo。作者显然没有真正理解关于“释放Zalgo”的文章,并且成功地重新实现了jQuery的承诺,由于许多原因,这些承诺被广泛认为是可怕的违背,包括它们的Zalgo性。

Synchronous promises are simply not safe, no matter your implementation.

同步承诺根本不安全,无论您的实现如何。