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

北战南征 提交于 2020-04-15 14:54:53

问题


In the following code,

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

promises entry_save and save_state are both readwrite database transactions and get_HTML is readonly. The two readwrite transactions could be combined together but that complicates the undo/redo chain that is maintained and it ties the success and rollback of the two together which is undesired.

The entry_save transaction needs to write before the save_state transaction. Before moving entry_save into the Promise.allSettled that is how it worked because the entry_save transaction was created prior to those of the others. This MDN article explains how the order in which requests are performed is based upon when the transactions are created independently of the order in which the requests are made.

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?

Although it works and is quick enough, I'd prefer to not do this:

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

If it matters, that's not the exactly the way it is written, it's more consistent with:

entry_save().then( intermediate ); where intermediate invokes the Promise.allSettled.

Thank you.

To clarify a bit, below is the example given in the above cited MDN document.

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.

If entry_save creates trans1 and save_state create trans2, and all in the synchronous code of the functions, meaning not within an onsuccess or onerror handler of a database request or something similar, will the MDN example not hold?

Thus, where @jfriend00 writes,

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.

will this order the timing of the write requests by that of the creation of the transactions, since the transactions are created in the synchronous code before the asynchronous can commence?

I'd like to test it but I'm not sure how. If two nearly identical promises are used in a Promise.allSettled, how can the write request of the first created transaction be delayed such that it takes place after the write request of the second created transaction, to test if it will be written first? A setTimeout should terminate the transaction. Perhaps a long-running synchronous loop placed before the request.


The code at the very end of this question may better illustrate more precisely what I have attempted to ask. It takes the MDN example in the article cited above and spreads it across two promises placed in a Promise.allSettled, both of which attempt to write to the same object store from within the onsuccess event of a get request.

The question was will the same principle in the article of the first transaction created writing before the second transaction created, regardless of the order the requests are made, still hold in this set up. Since the synchronous portions of the promises will process in the order the promises are placed in the array, the transaction in promise p_1 will be created before that of p_2. However, the put request in the onsuccess event of the get request in p_1 is delayed by the loop building a large string. The question is will p_1 still write before p_2?

In experimenting with this, I cannot get p_2 to write before p_1. Thus, it appears that the MDN example applies even in this type of set up. However, I cannot be sure of why because I don't understand how the JS code is really interpreted/processed.

For example, why can the req.onsuccess function be defined after the request is made? I asked that question sometime ago but still don't know enough to be sure that it doesn't affect the way I attempted to add in a delay here. I know that it won't work the other way around; but my point is I'm not sure how the browser handles that synchronous loop before the put request is made in p_1 to really know for sure that this example demonstrates that the MDN article ALWAYS holds in this set up. However, I can observe that it takes longer for the requests to complete as the number of loop iterations is increased; and, in all cases I have observed, p_1 always writes before p_2. The only way p_2 writes before p_1 is if p_1 doesn't write at all because of the string taking up to much memory causing the transaction in p_1 to be aborted.

That being said, and returning to the fuller set up of my question concerning three promises in the array of the Promise.allSettled compared to requiring entry_save to complete before commencing a Promise.allSettled on the two remaining promises, in the full code of my project, for reasons I am not sure of, the latter is quicker than the former, that is, waiting for entry_save to complete is quicker than including it in the Promise.allSettled.

I was expecting it to be the other way around. The only reason I can think of at this point is that, since entry_save and save_state are both writing to the same object store, perhaps whatever the browser does equivalent to locking the object store until the first transaction, which is that in entry_save, completes and removing the lock takes longer than requiring that entry_save complete before the Promise.allSettled commences and not involving a lock. I thought that everything would be ready "in advance" just waiting for the two put requests to take place in transaction order. They took place in order but more slowly or at least not as quick as using:

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

instead of:

 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 );
          };
    }); }

回答1:


indexedDB will maintain the order of the transactions in order created, except when those transactions do not overlap (e.g. do not involve the same store out of the set of stores each one involves). this is pretty much regardless of what you do at the higher promise layer.

at the same time, maybe it is unwise to rely on that behavior, because it is implicit and a bit confusing. so maybe it is ok to linearize with promises. the only reach catch is when you need maximum performance, which I doubt applies.

  • see https://www.w3.org/TR/IndexedDB-2/#transaction-construct
  • see are indexeddb/localforage reads resolved from a synchronous buffer?

moreover, promises begin execution at the time they are created. they just do not necessarily end at that time, they end eventually instead of immediately. that means that the calls happen in the order you 'create' the promises that are wrapping the indexedDB calls. which means that it relies on the order in which you create the transactions.

regardless of which promise wins the race. regardless of using promise.all.

also, promise.all will retain order even if promises complete out of order, just fyi, but do not let that throw you off.




回答2:


When you do this:

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

It's equivalent to this:

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

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

So, the individual function calls you issue such as save_state() are STARTED in the order specified. But, each of those calls are asynchronous so the internal order of what happens before something else really depends upon what they do inside as they can all be in flight at the same time and parts of their execution can be interleaved in an indeterminate order.

Imagine that entry_save() actually consists of multiple asynchronous pieces such as first reading some data from disk, then modifying the data, then writing it to the database. It would call the first asynchronous operation to read some data from disk and then immediately return a promise. Then, save_state() would get to start executing. If save_state() just immediately issued a write to the database, then it very well may write to the database before entry_save() writes to the database. In fact, the sequencing of the two database writes is indeterminate and racy.

If you need entry_save() to complete before save_state(), then the above is NOT the way to code it at all. Your code is not guaranteeing that all of entry_save() is done before any of save_state() runs.

Instead, you SHOULD do what you seem to already know:

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

Only that guarantees that entry_save() will complete before save_state() gets to run. And, this assumes that you're perfectly OK with save_state() and get_HTML() running concurrently and in an unpredictable order.

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?

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. After that, they are all in-flight at the same time and the internal timing between them depends upon how long their individual asynchronous operations take and what those asynchronous operations do. If order matters, you can't just put them all in an indeterminate race. That's call a "race condition". Instead, you would need to structure your code to guarantee that the desired operation goes first before the ones that need to execute after it.



来源:https://stackoverflow.com/questions/60290456/order-of-promise-array-in-promise-allsettled-and-order-in-which-database-transac

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!