【问题标题】:Catching errors and rollbacks in indexedDB在 indexedDB 中捕获错误和回滚
【发布时间】:2019-03-24 05:20:16
【问题描述】:

我必须在这里遗漏一些非常基本的东西,如果您能提供任何指导,我将不胜感激。谢谢。

当注释掉的 try-catch 与 catch 中的 abort 事务处于活动状态时,我认为以下代码正在工作。中止事务在每个先前成功的 req 上触发了 req.onerror,而每个成功的 req 又触发了 transaction.onerror 和太多递归错误。我解决了这个问题,并且知道的不多,认为一切正常,以至于个别请求错误触发 req.onerror 并回滚事务。然而,似乎只有在 catch 中的 abort 语句才会触发它。我通过发送无法解析为 JSON 字符串的错误数据来测试错误场景。

但是,现在,我在不需要 try-catch 和随后的中止的场景中使用相同的代码,我无法触发 req.onerror 并且不明白为什么。

传递给 DB_pop 的参数 o 是一个对象,其中包含对象存储的名称、一个数据对象数组和一个用于“add”或“put”的字符串。我故意传递一个好的数据对象和一个带有空键的数据对象,如下面的第一个代码语句所示。好的会写,坏的不会。 req.onerror 和 transaction.onerror 不会触发。只有 transaction.oncomplete 触发。此外,只有一个 req.onsuccess 触发。我得出结论是因为控制台日志中写入的内容。

连接到调用 DB_pop 函数的 Promise 的 then 语句运行拒绝函数。回滚函数运行,关闭then 清理变量以防止内存泄漏运行。我也不明白这一点,因为如果仅触发了 transaction.oncomplete,它应该解决而不是拒绝。

为什么不会为第二个数据对象触发 req.onerror 并导致事务回滚并删除写入的第一个数据对象?

我尝试将 req 设为一个数组,并删除 d 循环并使用 null 键传递单个数据对象,并且不会触发 onerror 事件。

我确实注意到,如果在现有密钥上将错误作为add 编造到数据库中,那么所有错误事件都会触发,甚至会触发数据库打开/创建中设置的一般错误事件。 put 中的空键似乎不会按预期触发错误并回滚。

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

【问题讨论】:

  • 也许尝试在您的拒绝回调中打印原因:p.then(() => { /* fulfilled */ }, (reason) -> console.log(reason))。 executor 函数中的异常会出现在这里。
  • 感谢您的回复。它确实打印了一个原因,即“DataError:提供给操作的数据不符合要求”。我认为这是因为密钥为空。问题是,即使有这个错误响应,事务在任何级别都没有事件错误的情况下完成,但是当它应该回滚时它只是部分完成。它似乎适用于对现有键的添加尝试,因为我后来添加到我的问题中,但在放置尝试中不是空键。

标签: indexeddb


【解决方案1】:

IDBObjectStore.put 抛出 DataError 因为提供的密钥无效。这会退出函数并阻止请求被创建(或执行 - 因此永远不会调用请求回调)。

事务正常完成,因为之前执行的所有数据库操作都成功了。承诺本身失败,因为该函数在尝试创建无效请求的行抛出了异常:

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

您可以在错误处理程序中为您的承诺显式调用abort 或在处理函数中捕获异常。

【讨论】:

  • 谢谢。与在我的代码中触发所有预期错误事件的现有键的添加尝试相比,您的意思是 DataError 的处理方式与 ConstraintError 不同,因此与您写的关于 DataError 没有创建请求的内容形成对比, ConstraintError 仅在对有效键创建请求后才出现,后来确定有效键已存在于对象存储中?对于像我这样的新手来说,这是一个棘手的概念。 try/catch 会捕获 DataError 吗?当然,我会测试一下看看。谢谢。
  • 许多 API 函数可能会引发同步异常,因此在创建事务后的所有内容周围加上 try/catch 可以让您处理这些错误和相关的处理错误。
  • 谢谢。将请求放入 try/catch 并在 catch 中调用 abort 解决了这个问题。我只是天真地假设 onerror 会捕获任何错误。因此,现在我的指针数据可以与实际数据保持一致。我将不得不开始更加关注 MDN 文档中的 Exceptions 表。再次感谢。
【解决方案2】:

我的第一个猜测是因为您在循环中定义了一个函数。关键问题是这种模式:

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

在循环中定义函数有很多有效的方法,但上面的例子不是一种。在循环中定义一个函数通常是一件很麻烦的事情,所以按照惯例,我建议完全避免它。试试这个模式:

function outerfunction() {
   function innerhelperfunction(){}

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

例如,请参阅Javascript: Creating Functions in a For Loop

【讨论】:

  • 感谢您的回复。我了解一些关于闭包的知识,并且遇到了您之前链接的问题中提到的问题,即变量的值是在函数声明时设置还是在执行时设置。我会试一试,但没有“外部”变量,只有事件和“this”。此外,它似乎适用于对现有键的添加尝试,并触发该测试场景中的所有错误事件,并且它适用于先前在 JSON.parse 尝试中使用 try-catch 时的手动中止。但是,我会试一试。谢谢。
猜你喜欢
  • 2016-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-03
  • 1970-01-01
  • 2019-01-21
  • 2011-06-18
  • 1970-01-01
相关资源
最近更新 更多