Promise.allSettled 中的承诺数组顺序和创建数据库事务的顺序?

Order of promise array in Promise.allSettled and order in which database transactions are created?

在下面的代码中,

Promise.allSettled( [ entry_save(), save_state(), get_HTML() ] ).then( ... );

承诺 entry_savesave_state 都是 readwrite 数据库事务,get_HTMLreadonly。两个 readwrite 事务可以组合在一起,但这会使维护的 undo/redo 链复杂化,并且它将两者的成功和回滚联系在一起,这是不希望的。

entry_save事务需要在save_state事务之前写入。在将 entry_save 移动到 Promise.allSettled 之前,它是这样工作的,因为 entry_save 交易是在其他交易之前创建的。此 MDN article 解释了执行请求的顺序如何基于事务的创建时间,而与请求的顺序无关。

我的问题是每个promise进程的同步代码是否按照它在数组中的顺序排列,这样放在entry_save前面总是会导致它的事务首先被创建并保证它的数据库将首先执行请求?

虽然它有效并且足够快,但我宁愿不这样做:

entry_save().then( () => { Promise.allSettled( [ save_state(), get_HTML() ] ) } ).then( ... );

如果这很重要,那不是它的写法,它更符合:

entry_save().then( intermediate ); 其中 intermediate 调用 Promise.allSettled.

谢谢。

为了澄清一点,下面是上面引用的 MDN 文档中给出的示例。

var trans1 = db.transaction("foo", "readwrite");
var trans2 = db.transaction("foo", "readwrite");
var objectStore2 = trans2.objectStore("foo")
var objectStore1 = trans1.objectStore("foo")
objectStore2.put("2", "key");
objectStore1.put("1", "key");

After the code is executed the object store should contain the value "2", since trans2 should run after trans1.

如果 entry_save 创建 trans1 并且 save_state 创建 trans2,并且所有这些都在函数的同步代码中,这意味着不在 onsuccessonerror 数据库请求的处理程序或类似的东西,MDN 示例是否不成立?

因此,@jfriend00 写道,

The functions are called in the order they are placed in the array, but that only determines the order in which the asynchronous are started.

由于事务是在异步代码开始之前在同步代码中创建的,因此这会根据事务的创建时间对写入请求的时间进行排序吗?

我想测试一下,但不确定如何进行。如果在Promise.allSettled中使用了两个几乎相同的promise,如何将第一个创建的事务的写请求延迟到第二个创建的事务的写请求之后,测试是否会先写? setTimeout 应该终止事务。也许在请求之前放置了一个 long-运行 同步循环。


这个问题最后的代码可能会更好地更准确地说明我试图问的问题。它采用上面引用的文章中的 MDN 示例,并将其分布在 Promise.allSettled 中的两个承诺中,这两个承诺都试图从 [=42] 的 onsuccess 事件中写入相同的对象存储=]请求。

问题是在创建第二个事务之前创建第一个事务的文章中的相同原则,无论发出请求的顺序如何,仍然适用于此设置。由于承诺的同步部分将按照承诺在数组中的顺序进行处理,因此承诺 p_1 中的交易将在 p_2 之前创建。但是,p_1中的get请求的onsuccess事件中的put请求被构建大字符串的循环延迟了。问题是 p_1 还会写在 p_2 之前吗?

在试验中,我无法让 p_2p_1 之前写入。因此,看起来 MDN 示例甚至适用于这种类型的设置。但是,我不能确定为什么,因为我不明白 JS 代码是如何真正 interpreted/processed.

比如req.onsuccess函数为什么可以在请求之后定义?我前段时间问过 但仍然不够了解以确保它不会影响我尝试在此处添加延迟的方式。我知道反过来不行;但我的观点是,我不确定在 p_1 中发出放置请求之前浏览器如何处理该同步循环,以真正确定此示例演示了 MDN 文章始终适用于此设置。但是,我可以观察到随着循环迭代次数的增加,请求完成所需的时间会更长;而且,在我观察到的所有情况下,p_1 总是写在 p_2 之前。 p_2p_1 之前写入的唯一方法是如果 p_1 根本不写入,因为字符串占用大量内存导致 p_1 中的事务中止。

也就是说,回到我的问题的更完整的设置,关于 Promise.allSettled 数组中的三个承诺与要求 entry_save 在开始 Promise.allSettled 之前完成相比关于剩下的两个承诺,在我的项目的完整代码中,由于我不确定的原因,后者比前者更快,也就是说,等待 entry_save 完成比将它包含在 Promise.allSettled

我原以为是相反的。在这一点上我能想到的唯一原因是,由于 entry_savesave_state 都写入同一个对象存储,也许无论浏览器做什么都等同于锁定对象存储直到第一次交易,也就是说,在 entry_save 中,完成和删除锁比要求 entry_savePromise.allSettled 开始之前完成并且不涉及锁花费的时间更长。我以为一切就绪 "in advance" 只是等待两个 put 请求按交易顺序发生。它们按顺序发生,但速度较慢或至少不如使用:

entry_save().then( () => { Promise.allSettled( [ save_state(), get_HTML() ] ) } ).then( ... );

而不是:

 Promise.allSettled( [ entry_save(), save_state(), get_HTML() ] ).then( ... );

function p_all() { Promise.allSettled( [ p_1(), p_2() ] ); }

function p_1()
  {
    return new Promise( ( resolve, reject ) =>
      {
        let T = DB.transaction( [ 'os_1', 'os_2' ], 'readwrite' ),
            q = T.objectStore( 'os_1' ),
            u = T.objectStore( 'os_2' ),
            req, i, t ='', x = '';    

     req = q.get( 1 );

     req.onsuccess = () =>
       {
         let i, t, r = req.result;
         for ( i = 1; i < 10000000; i++ ) t = t + 'This is a string';
         r.n = 'p1';
         u.put( r );
         console.log( r );
       };
    }); }

function p_2()
  {
    return new Promise( ( resolve, reject ) =>
      {
       let T = DB.transaction( [ 'os_1', 'os_2' ], 'readwrite' ),
           q = T.objectStore( 'os_1' ),
           u = T.objectStore( 'os_2' ),
           req;    

        req = q.get( 1 );

        req.onsuccess = () =>
          {
            let r = req.result;
            r.n = 'p2';
            u.put( r );
            console.log( r );
          };
    }); }

当你这样做时:

Promise.allSettled( [ entry_save(), save_state(), get_HTML() ] ).then(...)

相当于:

const p1 = entry_save();
const p2 = save_state();
const p3 = get_HTML();

Promise.allSettled([p1, p2, p3]).then(...);

因此,您发出的单个函数调用(例如 save_state())将按照指定的顺序启动。但是,这些调用中的每一个都是异步的,因此在其他事情之前发生的内部顺序实际上取决于它们在内部所做的事情,因为它们可以同时在飞行中并且它们的部分执行可以以不确定的顺序交错。

想象一下,entry_save()实际上由多个异步部分组成,例如首先从磁盘读取一些数据,然后修改数据,然后将其写入数据库。它会调用第一个异步操作从磁盘读取一些数据,然后立即 return 一个承诺。然后,save_state() 将开始执行。如果 save_state() 立即向数据库发出写入,那么它很可能会在 entry_save() 写入数据库之前写入数据库。事实上,这两个数据库写入的顺序是不确定的和活泼的。

如果您需要 entry_save()save_state() 之前完成,那么上面的代码根本不是编码方式。您的代码不保证所有 entry_save() 都在 save_state() 运行 中的任何一个之前完成。

相反,您应该做您似乎已经知道的事情:

entry_save().then( () => { Promise.allSettled( [ save_state(), get_HTML() ] ) } ).then( ... );

只有这样才能保证 entry_save() 会在 save_state() 到达 运行 之前完成。而且,这假设您完全可以同时使用 save_state()get_HTML() 运行,并且顺序不可预测。

My question is does the synchronous code of each promise process in the order in which it is placed in the array, such that placing entry_save first will always result in its transaction being created first and guaranteeing its database requests will be performed first?

函数按照它们在数组中的顺序调用,但这只决定了异步启动的顺序。之后,它们都同时处于运行状态,它们之间的内部时序取决于它们各自的异步操作需要多长时间以及这些异步操作的作用。如果顺序很重要,你不能把它们都放在一个不确定的比赛中。那叫一个"race condition"。相反,您需要构建代码以保证所需的操作先于需要执行的操作。

indexedDB 将按照创建的顺序维护事务的顺序,除非这些事务不重叠(例如,不涉及每个涉及的商店集中的同一商店)。这几乎与您在更高承诺层做什么无关。

同时,依赖这种行为也许是不明智的,因为它是隐含的并且有点令人困惑。所以也许用承诺线性化是可以的。唯一的障碍是当你需要最高性能时,我怀疑这是否适用。

此外,承诺在创建时开始执行。他们只是不一定在那个时候结束,他们最终而不是立即结束。这意味着调用按照您 'create' 包装 indexedDB 调用的承诺的顺序发生。这意味着它依赖于您创建交易的顺序。

无论哪个承诺赢得比赛。不管使用 promise.all.

此外,promise.all 即使 promise 完成时出现乱序,也会保持顺序,仅供参考,但不要让它让你失望。