1

I must be missing something very fundamental here and would appreciate any direction you may be able to provide. Thank you.

The following code is something that I thought was working when the commented out try-catch with the abort transaction in the catch was active. The abort transaction was triggering a req.onerror on every previous successful req, each of which in turn was triggering transaction.onerror and a too-much-recursion error. I worked that out and, not knowing much, thought everything was working fine such that individual request errors were triggering req.onerror and rollingback the transaction. However, it appears that it was only the abort statement in the catch that set that off. I was testing the error scenarios by sending bad data that couldn't be parsed as a JSON string.

But, now, that I'm using the same code in a scenario without the need for the try-catch and subsequent abort, I can't get the req.onerror to fire and don't understand why.

Argument o passed to DB_pop is an object containing the name of the object store, an array of data objects, and a string for 'add' or 'put'. I purposely pass one good data object and one with a null key, as seen in the first code statement below. The good one gets written, the bad one does not. The req.onerror and transaction.onerror do not fire. Only the transaction.oncomplete fires. Also, just one req.onsuccess fires. I conclude this because of what gets written to console log.

The then statement connected to the promise that invokes the DB_pop function, runs the reject function. The rollback function runs and the closing then that cleans up the variables to protect against memory leaks runs. I don't understand this either because if only the transaction.oncomplete is being triggered, it should resolve instead of reject.

Why doesn't a req.onerror fire for the second data object and cause the transaction to rollback and remove the first data object written?

I tried making req an array, and also removing the d loop and passing a single data object with a null key and neither onerror event fires.

I did notice that if an error is fabricated as an add to the database on an existing key, then all the error events fire, even up to the general error event set up in the database open/create. It seems that a null key in a put doesn't trigger an error as expected and rollback.

p = DB_pop( { 'os' : 'topics', 'data' : [ { 'key' : 0, 'gap' : T.gap, 'max' : T.max, 'selKey' : key }, { 'key' : null, 'title' : t, 'created' : d, 'edited' : d } ], 'op' : 'put' } ); 

p.then( () => { T.title = t; }, rollback ).then( () => {  evt = key = t = T = d = p = m = null; console.log('cleaned up'); } );




  function DB_pop( o )  // ( os, data, op )
    { 
      return new Promise( ( resolve, reject ) =>
        {
          if ( !DB_open.base ) reject( 'Failed to populate database ' + DB_open.title + ' because the database is closed or does not exist.' );

          let t = DB_open.base.transaction( [ o.os ], 'readwrite' ),
              s = t.objectStore( o.os ),
              req, d;

          DB_pop.error = false;

          function free_RAM() 
           { 
             t = s = req = d = null;
           } // close free_RAM

             t.oncomplete = ( e ) =>
               {
                 console.log('trans complete');
                 resolve();
                 free_RAM();
                 e = null;
               }; // close t.oncomplete

             t.onerror = ( e ) =>
               {
                 //e.stopPropagation(); // Stop propagation up to the database error level in the database open block.
                 DB_pop.error = true;
                 reject(e);
                 console.log( 'Transaction error : ' + e.target.error );
                 free_RAM();
                 e = null;
               }; // close t.onerror

             t.onabort = ( e ) =>
               {
                 console.log( 'Transaction aborted : ' + e.target.error );
                 free_RAM();
                 e = null;
               }; // close t.onabort


            for ( d of o.data )
              { 

               // try
               //   { let q = JSON.parse( { 'x' : d, 'y' : 3 }   ); }
               // catch(e)
                //  { t.abort(); error = true; break; }  //?????????? Test what takes place if the try fails.

                req = s[ o.op ]( d );  // o.op is either 'add' or 'put'.
                req.k = d.key; 
                req.n = d.nbr;

                req.onsuccess = function( e )
                  {
                    if ( !DB_pop.error )
                      {
                        console.log( 'Success at k = ' + this.k  + '.' );
                      }; // end if
                  }; // close req.onsuccess

                req.onerror = function( e ) 
                  { 
                    /*
                       When a transaction is rolled back/aborted, every request's error event is fired and propagates up to transaction.onerror and causes a "too much recursion" error.
                       Thus, let it propagate once and, within transaction.onerror, set error to true.
                    */

                    console.log( 'Request.onerror fired. Error = ' + e.target.error + ' k = ' + this.k + ', n = ' + this.n );

                    if ( DB_pop.error )
                      {
                        e.stopPropagation();
                        console.log( 'Stopping propagation' );
                      }; // end if
                  }; // close req.onerror

              }; // next d

        }); //  close promise

    } // close DB_pop
Gary
  • 2,393
  • 12
  • 31
  • Perhaps try printing the reason in your reject callback: `p.then(() => { /* fulfilled */ }, (reason) -> console.log(reason))`. An exception in the executor function would show up here. – jspcal Mar 24 '19 at 06:10
  • Thanks for responding. It does print a reason which is " DataError: Data provided to an operation does not meet requirements." I assume that is because the key is null. The problem is that, even with this error response, the transaction completes without event error at any level, but it only partially completes when it should roll back. It appears to work properly for an add attempt to an existing key, as I added to my question afterward, but not a null key in a put attempt. – Gary Mar 24 '19 at 06:16

2 Answers2

3

IDBObjectStore.put throws a DataError because the key that was provided is invalid. This exits the function and prevents the request from being created (or executed - so the request callbacks are never invoked).

The transaction completes normally because all the previously executed DB operations succeeded. The promise itself fails because the function threw an exception at the line that attempts to create the invalid request:

req = s[ o.op ]( d );

You could explicitly call abort in the error handler for your promise or catch exceptions within your processing function.

jspcal
  • 50,847
  • 7
  • 72
  • 76
  • Thank you. In comparison to the add attempt to an exsiting key, which triggers all the expected error events in my code, do you mean that a DataError is treated differently than a ConstraintError, such that in contrast to what you wrote about the DataError not creating the request, a ConstraintError only comes about after the request is created on a valid key and it is later determined that the valid key already exists in the object store? That's a tricky concept for the novice like me to pick up on. Would a try/catch catch the DataError? Of course, I'll test it out and see. Thanks. – Gary Mar 24 '19 at 06:53
  • Many API functions can throw synchronous exceptions, so a `try/catch` around everything after the transaction is created would let you handle those and related processing errors. – jspcal Mar 24 '19 at 07:04
  • Thank you. Putting the request in a try/catch and calling abort in the catch solved the problem. I just naively assumed that onerror would catch any error. So, now my pointer data can remain consistent with the actual data. I'll have to start paying closer attention to the Exceptions tables in the MDN documents. Thanks again. – Gary Mar 24 '19 at 07:12
0

My first guess is because you defined a function in a loop. The key issue is this pattern:

for(...) {
  request.onsuccess = function() {
    //...
  };
}

There are valid ways to define functions in loops, but the above example is not one. Defining a function within a loop is a messy business in general, so by convention I would recommend just avoiding it entirely. Try this pattern instead:

function outerfunction() {
   function innerhelperfunction(){}

   for(...) {
     request.onsuccess = innerhelperfunction;
   }
}

See, for example, Javascript: Creating Functions in a For Loop

Josh
  • 17,834
  • 7
  • 50
  • 68
  • Thanks for responding. I understand some about closures and have run into the problem mentioned in the question you linked to before, concerning whether a variable's value is set at the time a function is declared or at the time it is executed. I'll give it a try but there are no "external" variables, just the event and "this". Also, it appears to work for an add attempt to an existing key, and triggers all the error events in that test scenario, and it worked for the manual abort in the previous use of the try-catch on the JSON.parse attempt. But, I'll give it a try. Thanks. – Gary Mar 24 '19 at 06:41