hydrogen-web/doc/error-handling.md

3.1 KiB

Error handling

Ideally, every error that is unexpected and can't be automatically recovered from without degrading the experience is shown in the UI. This is the task of the view model, and you can use ErrorReportViewModel for this purpose, a dedicated base view model class. It exposes a child view model, ErrorViewModel, when reportError is called which can be paired with ErrorView in the view to present an error message from which debug logs can also be sent.

Methods on classes from the matrix layer can often throw errors and those errors should be caught in the view model and reported to the UI. When inheriting from ErrorReportViewModel, there is the low-level reportError method, but typically you'd use the convenience method logAndCatch. The latter makes it easy to get both error handlikng and logging right. You would typically use logAndCatch for every public method in the view model (e.g methods called from the view or from the parent view model). It calls a callback within a log item and also a try catch that reports the error.

Sync errors & ErrorBoundary

There are some errors that are thrown during background processes though, most notably the sync loop. These processes are not triggered by the view model directly, and hence there is not always a method call they can wrap in a try/catch. For this, there is the ErrorBoundary utility class. Since almost all aspects of the client can be updated through the sync loop, it is also not too helpful if there is only one try/catch around the whole sync and we stop sync if something goes wrong.

Instead, it's more helpful to split up the error handling into different scopes, where errors are stored and not rethrown when leaving the scope. One example is to have a scope per room. In this way, we can isolate an error occuring during sync to a specific room, and report it in the UI of that room. This is typically where you would use reportError from ErrorReportViewModel rather than logAndCatch. You observe changes from your model in the view model (see docs on updates), and if the error property is set (by the ErrorBoundary), you call reportError with it. You can do this repeatedly without problems, if the same error is already reported, it's a No-Op.

writeSync and preventing data loss when dealing with errors.

There is an extra complication though. The writeSync sync lifecycle step should not swallow any errors, or data loss can occur. This is because the whole writeSync lifecycle step writes all changes (for all rooms, the session, ...) for a sync response in one transaction (including the sync token), and aborts the transaction and stops sync if there is an error thrown during this step. So if there is an error in writeSync of a given room, it's fair to assume not all changes it was planning to write were passed to the transaction, as it got interrupted by the exception. Therefore, if we would swallow the error, data loss can occur as we'd not get another chance to write these changes to disk as we would have advanced the sync token. Therefore, code in the writeSync lifecycle step should be written defensively but always throw.