@@ -25,6 +25,12 @@ import { PersistencePromise } from './persistence_promise';
25
25
26
26
const LOG_TAG = 'SimpleDb' ;
27
27
28
+ /**
29
+ * The maximum number of retry attempts for an IndexedDb transaction that fails
30
+ * with a DOMException.
31
+ */
32
+ const TRANSACTION_RETRY_COUNT = 3 ;
33
+
28
34
export interface SimpleDbSchemaConverter {
29
35
createOrUpgrade (
30
36
db : IDBDatabase ,
@@ -242,32 +248,56 @@ export class SimpleDb {
242
248
} ;
243
249
}
244
250
245
- runTransaction < T > (
251
+ async runTransaction < T > (
246
252
mode : 'readonly' | 'readwrite' ,
253
+ idempotent : boolean ,
247
254
objectStores : string [ ] ,
248
255
transactionFn : ( transaction : SimpleDbTransaction ) => PersistencePromise < T >
249
256
) : Promise < T > {
250
- const transaction = SimpleDbTransaction . open ( this . db , mode , objectStores ) ;
251
- const transactionFnResult = transactionFn ( transaction )
252
- . catch ( error => {
253
- // Abort the transaction if there was an error.
254
- transaction . abort ( error ) ;
255
- // We cannot actually recover, and calling `abort()` will cause the transaction's
256
- // completion promise to be rejected. This in turn means that we won't use
257
- // `transactionFnResult` below. We return a rejection here so that we don't add the
258
- // possibility of returning `void` to the type of `transactionFnResult`.
259
- return PersistencePromise . reject < T > ( error ) ;
260
- } )
261
- . toPromise ( ) ;
262
-
263
- // As noted above, errors are propagated by aborting the transaction. So
264
- // we swallow any error here to avoid the browser logging it as unhandled.
265
- transactionFnResult . catch ( ( ) => { } ) ;
266
-
267
- // Wait for the transaction to complete (i.e. IndexedDb's onsuccess event to
268
- // fire), but still return the original transactionFnResult back to the
269
- // caller.
270
- return transaction . completionPromise . then ( ( ) => transactionFnResult ) ;
257
+ let attemptNumber = 0 ;
258
+
259
+ while ( true ) {
260
+ ++ attemptNumber ;
261
+
262
+ const transaction = SimpleDbTransaction . open ( this . db , mode , objectStores ) ;
263
+ try {
264
+ const transactionFnResult = transactionFn ( transaction )
265
+ . catch ( error => {
266
+ // Abort the transaction if there was an error.
267
+ transaction . abort ( error ) ;
268
+ // We cannot actually recover, and calling `abort()` will cause the transaction's
269
+ // completion promise to be rejected. This in turn means that we won't use
270
+ // `transactionFnResult` below. We return a rejection here so that we don't add the
271
+ // possibility of returning `void` to the type of `transactionFnResult`.
272
+ return PersistencePromise . reject < T > ( error ) ;
273
+ } )
274
+ . toPromise ( ) ;
275
+
276
+ // As noted above, errors are propagated by aborting the transaction. So
277
+ // we swallow any error here to avoid the browser logging it as unhandled.
278
+ transactionFnResult . catch ( ( ) => { } ) ;
279
+
280
+ // Wait for the transaction to complete (i.e. IndexedDb's onsuccess event to
281
+ // fire), but still return the original transactionFnResult back to the
282
+ // caller.
283
+ await transaction . completionPromise ;
284
+ return transactionFnResult ;
285
+ } catch ( e ) {
286
+ const retryable =
287
+ idempotent &&
288
+ isDomException ( e ) &&
289
+ attemptNumber < TRANSACTION_RETRY_COUNT ;
290
+ debug (
291
+ 'Transaction failed with error: %s. Retrying: %s.' ,
292
+ e . message ,
293
+ retryable
294
+ ) ;
295
+
296
+ if ( ! retryable ) {
297
+ return Promise . reject ( e ) ;
298
+ }
299
+ }
300
+ }
271
301
}
272
302
273
303
close ( ) : void {
@@ -755,3 +785,15 @@ function checkForAndReportiOSError(error: DOMException): Error {
755
785
}
756
786
return error ;
757
787
}
788
+
789
+ /**
790
+ * Checks whether an error is a DOMException (e.g. as thrown by IndexedDb).
791
+ *
792
+ * Supports both browsers and Node with persistence.
793
+ */
794
+ function isDomException ( e : Error ) : boolean {
795
+ return (
796
+ ( typeof DOMException !== 'undefined' && e instanceof DOMException ) ||
797
+ e . constructor . name === 'DOMException'
798
+ ) ;
799
+ }
0 commit comments