The try...catch
statement in TypeScript is similar to the one in JavaScript, which allows you to handle errors that may occur in your code. The try block contains the code that may throw an error, and the catch block contains the code that executes when an error is caught.
try {
// some code that may throw an error
} catch (error) {
// some code that handles the error
} finally {
// some code that will always run
}
throw
statementThe throw
statement in JavaScript (and TypeScript) is used to explicitly throw an exception. When an exception is thrown, it disrupts the normal flow of execution and transfers control to the nearest enclosing try...catch
statement that can handle the exception.
throw expression;
If no matching catch block is found within the current function, the exception propagates up the call stack to the next enclosing try…catch statement in an outer function. This process continues until a matching catch
block is found or until the exception reaches the global scope, resulting in an unhandled exception error.
You can throw any value in TypeScript, including primitive types like number, string, boolean, and symbol. You can also throw objects, arrays, functions, classes, and even custom types like Error or TypeError.
However, throwing values other than Error or its subclasses is generally considered a bad practice, because it makes it harder to handle the errors properly and provide useful information to the user or the developer.
While TypeScript provides static type checking for most parts of your code, exceptions are considered runtime errors that can occur in various scenarios. Since the type of thrown values is determined at runtime, the TypeScript compiler does not enforce specific types for thrown values.
The error parameter in the catch block represents the error object that was thrown by the try block. The only type annotations that are allowed on catch clause variables are any
or unknown
, which are the most general types in TypeScript.
Type any
is used by default, which means you can access any property or method on it without type checking. However, this can be unsafe and lead to runtime errors if the error object does not have the expected properties or methods.
You can specify the type of the error parameter as unknown
, which is a safer alternative to any
. This means you have to perform explicit type checking and requires type narrowing before accessing any property or method on the error object.
try {
// some code that may throw an error
} catch (error: unknown) {
// some code that handles the error
if (typeof error === "string") {
// handle string error
} else if (error instanceof Error) {
// handle Error object
} else {
// handle other types of errors
}
}
This option is available since TypeScript 4.0, and you can also enable it by default with the useUnknownInCatchVariables compiler option, then you do not need the additional syntax (: unknown)
nor a linter rule to try enforce this behavior.
The built-in Error class is a standard class in JavaScript that represents an error object. It has a name property that indicates the type of error, a message property that provides a human-readable description of the error, and a stack property that contains the stack trace of where the error occurred.
The built-in Error class can be used to create and throw custom errors, or to catch and handle errors thrown by other code. For example:
try {
// some code that may throw an error
throw new Error("Random error message");
} catch (err) {
// handle the error
if (err instanceof Error) {
console.log(err.name); // the type of error
console.log(err.message); // the description of the error
console.log(err.stack); // the stack trace of the error
} else {
// handle other errors
}
}
The built-in Error class is also used as a base class for other standard error classes, such as SyntaxError, TypeError, ReferenceError, etc. These classes represent different kinds of errors that may occur in JavaScript code, and have their own name and message properties.
You can create your own error types by extending the built-in Error class or standard error classes. Note that you must call super
in the child constructor, and also to set the name
property explicitly. Otherwise, the error name will be “Error”, which is not very informative.
class CustomError extends Error {
constructor(message: string) {
super(message); // call the parent constructor
this.name = "CustomError"; // set the name property
}
}
This way, you can throw and catch instances of CustomError and use the instanceof operator to check their type.
try {
throw new CustomError("Something went wrong");
} catch (err) {
if (err instanceof CustomError) {
console.log(err.name); // CustomError
console.log(err.message); // Something went wrong
} else {
// handle other errors
}
}
You can also add other properties or methods to your custom error class, such as a code, a data, or a log function.
class HttpError extends Error {
statusCode: number;
constructor(statusCode: number, message: string) {
super(message);
this.name = "HttpError";
this.statusCode = statusCode;
}
log() {
console.log(`Http error ${this.statusCode}: ${this.message}`);
}
}
You can use this class to throw and handle HTTP-related errors:
try {
throw new HttpError(404, "Not found");
} catch (err) {
if (err instanceof HttpError) {
err.log(); // Http error 404: Not found
} else {
// handle other errors
}
}
Custom error types allow you to define meaningful and descriptive error names that convey the specific nature of the error. This improves the clarity and readability of your code, making it easier to understand and maintain.
Handling errors in JavaScript and TypeScript is not much different from other languages. By following these best practices, you can create more reliable maintainable code, and ensure a better user experience when errors occur.