Best Practices
1. Understand Promises
Make sure you learn at least the basic practices of A+ promises before diving too deep into Dexie.
Here's a little test. Please review the code below and then ask yourself if you understood what it was doing...
Was there any of the "Understand" parts that you didn't understand? Then you would benefit from learning a little about promises. This will be useful knowledge whatever lib you'll be using. Some links to dive into:
2. Be wise when catching promises!
Short Version:
Long Version:
It's bad practice to do this everywhere:
It's much better to do just this:
If you catch a promise, your resulting promise will be considered successful. It's like doing try..catch in a function where it should be done from the caller, or caller's caller instead. Your flow would continue even after the error has occurred.
In transaction scopes, it is even more important to NOT catch promises because if you do, transaction will commit! Catching a promise should mean you have a way to handle the error gracefully. If you don't have that, don't catch it!
But on an event handler or other root-level scope, always catch! Why?! Because you are the last one to catch it since you are NOT returning Promise! You have no caller that expects a promise and you are the sole responsible of catching and informing the user about any error. If you don't catch it anywhere, an error will end-up in the standard unhandledrejection event.
Sometimes you really WANT to handle an explicit error because you know it can happen and you have a way to work around it.
In the above example, we are handling the error because we know it may happen and we have a way to solve that.
What about if you want to log stuff for debugging purpose? Just remember to rethrow the error if you do.
An equivalent async / await example:
3. Avoid using other async APIs inside transactions
IndexedDB will commit a transaction as soon as it isn't used within a tick. This means that you MUST NOT call any other async API (at least not wait for it to finish) within a transaction scope. If you do, you will get a TransactionInactiveError thrown at you as soon as you try to use the transaction after having waited for the other async API.
In case you really need to call a short-lived async-API, Dexie can actually keep your transaction alive for you if you use Dexie.waitFor() but it is not a recommendation to use it unless for very short async calls where there is no other way around (such as Crypto calls).
4. Use the global Promise within transactions!
Make sure to use the global promise (window.Promise) within transactions. You may use a Promise polyfill for old browsers like IE10/IE11, but just make sure to put it on window.Promise in your page bootstrap.
OK:
NOT OK:
In the case you write a library (not an app) and you want your library to work on old browsers without requiring a Promise polyfill, it is still safe to use Dexie.Promise:
STILL ALSO OK:
5. Use transaction() scopes whenever you plan to make more than one operation
Whenever you are going to do more than a single operation on your database in a sequence, use a transaction. This will not only encapsulate your changes into an atomic operation, but also optimize your code! Internally, non-transactional operations also use a transaction but it is only used in the single operation, so if you surround your code within a transaction, you will perform less costly operations in total.
Using transactions gives you the following benefits:
Robustness: If any error occur, transaction will be rolled back!
Simpler code: You may do all operations sequentially - they get queued on the transaction.
One single line to catch them all - exceptions, errors wherever they occur.
You can just fire off database operations without handling returned promises. The transaction block will catch any error explicitly.
Faster execution
Remember that a browser can close down at any moment. Think about what would happen if the user closes the browser somewhere between your operations. Would that lead to an invalid state? If so, use a transaction - that will make all operations abort if browser is closed between operations.
Here is how you enter a transaction block:
Notes:
friends
andpets
are objectStores registered using Version.stores() method."rw"
should be replaced with"r"
if you are just going to read from database.Also errors occurring in nested callbacks in the block will be caught by the catch() method.
6. Rethrow errors if transaction should be aborted
Saying this again.
When you catch database operations explicitly for logging purpose, transaction will not abort unless you rethrow the error or return the rejected Promise.
If not rethrowing the error, Nils would be successfully added and transaction would commit since the error is regarded as handled when you catch the database operation.
An alternate way of rethrowing the error is to replace throw error;
with return Promise.reject(error)
.