jakearchibald / idb

IndexedDB, but with promises
https://www.npmjs.com/package/idb
ISC License
6.22k stars 348 forks source link

[Question] How to prevent errors from failing transaction #256

Open MikeDabrowski opened 2 years ago

MikeDabrowski commented 2 years ago

Hi, I want to only add items to the db and ignore those that are already present in db. ID is unique, I did

async storeUsers(users: StoredUserPreview[]) {
    const tx = await this.db.transaction('users', 'readwrite');
    return Promise.all(
      users.map((user) => tx.store.add(user)).concat(tx.done as any),
    );
  }

but when at least one key is already in the db whole transaction is rejected.
I found this issue that is build in native indexeddb and seems to be resolved. Is this possible to adapt?

I know that there is an option to first search if the key exists using count. What is the difference of those two methods (count vs catching error) performance-wise ?

jakearchibald commented 2 years ago

Here's how I'd do it with the current library:

function preventTransationCloseOnError(promise) {
  const request = unwrap(promise);
  request.addEventListener('error', (event) => {
    event.preventDefault();
    event.stopPropagation();
  });
  return promise;
}

const tx = db.transaction('users', 'readwrite');
return Promise.all(
  ...users.map((user) => preventTransationCloseOnError(tx.store.add(user))),
  tx.done,
);

However, this seems like a rough edge with the library, so I'll think about a better way to do this in a future version.

jakearchibald commented 2 years ago

unwrap is a function exported by the library.

MikeDabrowski commented 2 years ago

Thanks! I will test it later today. Is this approach even sensible? I feel like cheating a bit.

Yesterday I managed to use count to get to the same result. Maybe for reference if anyone else needs it:

async storeUsers(users: StoredUser[]) {
    const usersToAdd = []
    for (const user of users) {
      const isUserNotInDb = await this.db.countFromIndex('users', 'userId', user.id) === 0;
      if(isUserNotInDb) {
        usersToAdd.push(user);
      }
    }
    const tx = await this.db.transaction('users', 'readwrite');
    return Promise.all(
        usersToAdd.map(user => tx.store.add(user)).concat(tx.done as any)
    );
  }
jakearchibald commented 2 years ago

My gut feeling is that the preventTransationCloseOnError approach will be faster.

I think the simplest way to put this into the library would be a ignoreConstraints method, that takes a promise wrapping a Request, and:

In addition, I need to ensure I'm only reacting to events with a matching target.

RobbieTheWagner commented 3 months ago

It would be great to have an option to have add not fail if the key already exists or perhaps to replace the data at the key.

jakearchibald commented 3 months ago

Isn't that exactly what set does?

RobbieTheWagner commented 3 months ago

Isn't that exactly what set does?

I'm not sure what you mean. I used store.put for my use case because I want the values to update each time, but store.add errors if the key already exists. It might be nice to have an option for it to not error.