Skip to content

Error Handling

Error handling in both languages is mostly congruent (as opposed to Go, for example). Both follow the try-catch-finally exception handling construct.

Error handling in C# is centered around exceptions, which are objects representing runtime errors. Unlike JavaScript and TypeScript, where errors can be any value (including simple strings), C# enforces structured exception handling using the try, catch, finally, and throw keywords. Exceptions in C# derive from System.Exception, allowing for a robust type hierarchy where specific exceptions (e.g., NullReferenceException, ArgumentException) provide more context about failures.

In contrast, JavaScript and TypeScript use a more flexible error-handling approach. While they support try...catch...finally, errors are not required to follow a strict class-based structure—any value, even a string, can be thrown. TypeScript improves error handling slightly by enabling type annotations, but it does not enforce exception types like C#. Additionally, unhandled promise rejections in JavaScript (from asynchronous operations) can silently fail if not explicitly caught, whereas C#’s Task and async methods throw exceptions that must be handled explicitly, reducing the risk of silent failures.

Throwing Exceptions

ts
throw new Error("Oops!");
csharp
throw new Exception("Oops!");

Try-Catch-Finally

ts
try {
  // Work here
} catch {
  // Handle error here
}

try {
  // Work here
} catch (err) {
  // Handle error here
} finally {
  // Always executed
}
csharp
try {
  // Work here
} catch {
  // Handle error here
}

try {
  // Work here
} catch (Exception ex) {
  // Handle error here
} finally {
  // Always executed
}

Exception Types

ts
class NotFoundError extends Error {
  constructor(message) {
    super(message)
  }
}
csharp
class NotFoundException : Exception {
  public NotFoundException(string message)
    : base(message) { }
}

// Using a primary constructor (see later docs)
class NotFoundException(
  string message
) : Exception(message) { }

Now we can filter on the type of exception. The mechanism is cleaner in C#.

ts
try {
  // Work here
} catch (err) {
  if (err instanceof NotFoundError) {
    // Handle NotFoundError
  } else {
    // Handle all other errors
  }
} finally {
  // Always executed
}
csharp
try {
  // Work here
} catch (NotFoundException) {
  // Handle NotFoundException
} catch (Exception) {
  // Handle all generic exceptions
} finally {
  // Always executed
}

Best Practices

Rethrowing

ts
try {
  // Work here
} catch (err) {
  // Handle then rethrow
  throw err;
} finally {
  // Always executed
}
csharp
try {
  // Work here
} catch (Exception) {
  // 👇 NOTE that this DOES NOT use `throw ex;`
  throw;
} finally {
  // Always executed
}

WARNING

In C#, catch(Exception ex) { throw; } is not the same as catch(Exception ex) { throw ex; }. In the former, the stack trace is maintained. In the latter, the stack trace will be reset. Prefer the former versus the latter!