Skip to content
This repository has been archived by the owner on Feb 13, 2021. It is now read-only.

Cancel errors? #6

Open
inexorabletash opened this issue Oct 15, 2015 · 4 comments
Open

Cancel errors? #6

inexorabletash opened this issue Oct 15, 2015 · 4 comments

Comments

@inexorabletash
Copy link
Owner

With the event-based API, an error on a request that is not prevented causes the transaction to abort:

var tx = db.transaction('team', 'readwrite');
var store = tx.objectStore('team');
var r1 = store.add({name: 'alice'}, 1);
var r2 = store.add({name: 'bob'}, 1);
r2.onerror = function(e) {
  assert(r2.error.name === 'ConstraintError'); // due duplicate IDs
  e.preventDefault(); // otherwise, transaction will abort
};

Is there a Promise-friendly way to express this?

@inexorabletash
Copy link
Owner Author

inexorabletash commented Oct 26, 2015

We need to precisely specify promise resolution & microtask execution vs. event dispatch. Are events dispatched before the request's promise is fulfilled or after?

If after, it's too late by the time a rejection runs, since the transaction is aborted once control returns from the event dispatch if the event's default is not prevented. Options are (1) require use of event handlers here or (2) introduce some property of the request e.g. .doNotAbortOnError (but with a better name). Note that that's still awkward since you need to capture the request:

let r2 = store.add({name: 'bob'}, 1);
r2.ready.catch((reason) => {r2.doNotAbortOnError = true;});
await r2.ready;

If before... same problem as (2) above. What do you operate on to prevent the default?

Horrible idea: introduce a DOMException subclass with a .preventDefault() method, and use this for ConstraintError and other cases here. Then rewire things so the promise rejection handlers get to run before the transaction gets aborted. Problem - the rejection handlers may get propagated through various async hops, and it wouldn't be clear which would get a crack at preventing the default.

@inexorabletash
Copy link
Owner Author

Hallway conversations with @littledan - no solutions, still gathering thoughts.

To clarify the problem a bit more, if transactions were not automatically aborted here's how you could write it:

const tx = db.transaction('team', 'readwrite');
const store = tx.objectStore('team');
tx.waitUntil((async () => {
  try {
    await Promise.all([
      store.add({name:'alice'}, 1),
      store.add({name: 'bob'}, 1)
    ]);
  } catch (ex) {
    assert(ex.name ===  'ConstraintError');
  }
})());

It's even worse if you want to do it just on the second request. Regardless, that won't work as-is since the failure of the second request already aborts the transaction, regardless of how it's consumed.

Note that you can catch exceptions on the transaction, and you will normally bind a variable to a transaction even in async code, so this is actually valid/correct:

const tx = db.transaction('team', 'readwrite');
const store = tx.objectStore('team');
tx.onerror = function(ev) {
  assert(tx.error.name === 'ConstraintError'); // due duplicate IDs
  ev.preventDefault(); // otherwise, transaction will abort
};
store.add({name:'alice'}, 1);
store.add({name: 'bob'}, 1);
await tx.complete;

Another idea: (3) add options to every request type that can fail with preventable errors (just put() and add() ???) allowing you to ignore errors. You wouldn't observe the errors, though - is that a blocker?

@littledan
Copy link

Another thought to gather of a related construct, but it feels to me like a dead end:

There's a related concept in Promises, the "unhanded Promise rejection callback". Each Promise has a [[PromiseIsHandled]] internal slot, which says whether it's unhandled or not. If only you could abort the transaction on unhandled Promises, it could be resolved. However, then you'd also want to make sure that the resulting Promise chain, if the rejection isn't handled, will abort the transaction. I don't see any great way to do this tracking--it seems really hard to tell whether a subsequent Promise rejection with no handler is related to a particular earlier Promise.

@dfahlander
Copy link

dfahlander commented Sep 27, 2016

Hi, as I've implemented a promise based indexedDB wrapper, Dexie.js, I just want to share that using Promise.catch() for preventing transactions from aborting has worked out very well, in compliance with @littledan 's suggestion.

I believe it would be the most logical way to do it.

In Dexie, any error (thrown or idb error event) will abort transaction unless you catch it. This means that you can catch idb error events in order to prohibit transaction from aborting.

If user for some reason wants to catch an operation for logging/debug purpose, he/she must rethrow the error if still wanting transaction to abort.

[https://github.com/dfahlander/Dexie.js/wiki/Best Practices#6-rethrow-errors-if-transaction-should-be-aborted](https://github.com/dfahlander/Dexie.js/wiki/Best Practices#6-rethrow-errors-if-transaction-should-be-aborted)

Think about how the code will function in async await flow: if you surround the operations with a try/catch, you tell your caller that this was handled. See the idb transaction as the caller.

  • If any error slips through, transaction should abort, no matter if it's an idb error or application error or wrongly typed variable.
  • If your code catches an error, consider it handled and commit transaction, unless the catch block throws or returns another promise that is being rejected.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants