Error Reporting
First we need to make the distinction between expected runtime
errors and exceptions. Unfortunately both are
represented with the Error class, but they're conceptually
different. Exceptions are fatal, errors are not.
The story around exceptions is simpler so let's start there.
Exceptions
An exception is an unexpected, fatal problem in the app itself. For
example, our old friend undefined is not a function. This
is a problem with the code itself which cannot be resolved at runtime.
Our only option is to quit the app and relaunch.
We handle uncaught exceptions by registering a global listener. We report the exception to Central, tell the user that an unrecoverable error happened, and then quit and relaunch. End of story.
Errors
Errors are a bit more involved. They are anything that can go wrong in the standard usage of the app. For example, if the internet's down or a git repository is in a funny state, we're gonna get some errors.
Our error reporting flows through the Dispatcher like
most everything in the app. postError
calls the registered
error
handlers, starting with the most recently registered. The error
handlers have the chance to pass the error through untouched, return a
different or more specific error, or swallow the error entirely.
Error handlers must have the following type:
export async function myCoolErrorHandler(
error: Error,
dispatcher: Dispatcher
): Promise<Error | null> {
// code goes here
}If an error passes through all the registered error handlers, the
final error handler will call Dispatcher#presentError.
That will present the generic error dialog to the user.
+------------------------+
| |
| Dispatcher#postError |
| |
+------------------------+
|
|
+------------------+ +--------------------+
| | | |
| error handlers |-----| do something else |
| | | |
+------------------+ +--------------------+
|
|
+-------------------------+
| |
| Dispatcher#presentError |
| |
+-------------------------+
Error subclasses
We define some Error subclasses that are used in the
codebase use to provide more context to error handlers:
GitError- wraps a rawErrorraise bydugitewith additional git information.ErrorWithMetadata- wraps an existingErrorwith additional metadata.
In addition to the global information like the repository associated
with the error, ErrorWithMetadata supports providing
additional details:
- a
RetryActioncan be set to let the user retry the action that previously failed, allowing error handlers the ability to retry whatever action caused the error. - a
IGitErrorContextcan be set to add custom details about the Git operation that failed, so that error handlers have more context to provide the user about how to recover