Gary
Gary

Reputation: 2859

Catching errors and rollbacks in indexedDB

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

Upvotes: 2

Views: 1802

Answers (2)

jspcal
jspcal

Reputation: 51904

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.

Upvotes: 3

Josh
Josh

Reputation: 18690

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

Upvotes: 1

Related Questions