Dart Exception Handling
An exception is an error event that occurs during program execution.
Good exception handling allows the program to degrade gracefully when encountering errors, rather than crashing directly.
This chapter introduces try/catch/finally in Dart, throwing exceptions with throw, catching specific types with on, and rethrowing with rethrow.
try / catch / finally Basics
The try block contains code that may throw exceptions, the catch block catches and handles exceptions, and the finally block executes regardless of whether an exception occurs.
Example
// Basic try-catch
try {
int result = 10 ~/ 0; // Division by zero throws an exception
print(Result: $result); // This line will not execute
} catch (e) {
// e is the exception object
print(Caught exception: $e);
}
// Get exception and stack trace info
try {
List<int> numbers = [1, 2, 3];
print(numbers[10]); // Access out-of-bounds index
} catch (e, stackTrace) {
// e is the exception, stackTrace is the call stack
print(EXAMPLE exception: $e);
print(Stack trace information:\n$stackTrace');
}
// finally: executes regardless of whether an exception occurs
try {
print(Attempting to execute operation...);
int result = 5 ~/ 0;
print(This line will not execute: $result);
} catch (e) {
print(Error: $e);
} finally {
// Code in the finally block always executes, often used to clean up resources
print(Resource cleanup complete (finally always executes));
}
}
捕获到异常: IntegerDivisionByZeroException EXAMPLE 异常: RangeError (index): Invalid value: Not in inclusive range 0..2: 10 堆栈信息: #0 List.[] (dart:core-patch/growable_array.dart:264) #1 main (file:///...) ... 尝试执行操作... 出错: IntegerDivisionByZeroException 清理资源完成(finally 总是执行)
The finally block is best suited for resource cleanup, such as closing files, releasing database connections, etc. Even if there is a return statement in try or catch, finally still executes.
throw: Throw an exception
Using the throw keyword allows you to actively throw an exception.
Dart allows throwing any object as an exception (not just subclasses of Exception), but the best practice is to throw subclasses of Exception or Error.
Example
class InvalidAgeException implements Exception {
final int age;
final String message;
InvalidAgeException(this.age)
: message = Invalid age: $age (age must be between 0 and 150);
@override
String toString() => 'InvalidAgeException: $message';
}
// Function that uses a custom exception
void validateAge(int age) {
if (age < 0) {
throw InvalidAgeException(age);
}
if (age > 150) {
throw InvalidAgeException(age);
}
print(Age $age validated successfully);
}
// Throw built-in exception
int divide(int a, int b) {
if (b == 0) {
throw ArgumentError(Divisor cannot be 0); // Use built-in exception
}
return a ~/ b;
}
void main() {
// Test custom exception
try {
validateAge(-5);
} catch (e) {
print(e);
}
try {
validateAge(200);
} catch (e) {
print(e);
}
validateAge(25); // Normal case
// Test throwing built-in exception
try {
divide(10, 0);
} catch (e) {
print(EXAMPLE error: $e);
}
}
InvalidAgeException: 无效的年龄: -5(年龄必须在 0 到 150 之间) InvalidAgeException: 无效的年龄: 200(年龄必须在 0 到 150 之间) 年龄 25 验证通过 EXAMPLE 错误: Invalid argument(s): 除数不能为 0
The difference between Exception and Error: Exception is a predictable error that the program can handle (such as network timeout, file not found); Error is an unpredictable problem that usually indicates a program bug (such as type errors, null pointer). Your code should catch Exception, not Error.
on: Catch a specified type
The on keyword can catch exceptions by type, making error handling more fine-grained.
Example
// Catch different exceptions by type
try {
// Simulate different types of exceptions
int result = performOperation('divide_by_zero');
print(Result: $result);
} on IntegerDivisionByZeroException {
// Only catch division-by-zero exception
print(EXAMPLE error: cannot divide by zero);
} on FormatException catch (e) {
// Catch format exception and get exception information
print('EXAMPLE format error: ${e.message}');
} on RangeError {
// Only catch range errors
print('EXAMPLE error: index out of range');
} catch (e) {
// Fallback: catch all other exceptions
print('Unknown error: $e');
} finally {
print('Operation ended\n');
}
// Example of matching in order
print('--- Test different operations ---');
for (var op in ['divide_by_zero', 'parse_error', 'out_of_range', 'normal']) {
try {
performOperation(op);
} on IntegerDivisionByZeroException {
print('$op -> division by zero exception');
} on FormatException {
print('$op -> format exception');
} on RangeError {
print('$op -> range exception');
} catch (e) {
print('$op -> other exception: $e');
}
}
}
int performOperation(String operation) {
switch (operation) {
case 'divide_by_zero':
return 10 ~/ 0; // Throw IntegerDivisionByZeroException
case 'parse_error':
int.parse('not_a_number'); // Throw FormatException
return 0;
case 'out_of_range':
var list = [1, 2, 3];
return list[100]; // Throw RangeError
case 'normal':
return 42;
default:
throw Exception('Unknown operation');
}
}
EXAMPLE 错误: 不能除以零 操作结束 --- 测试不同的操作 --- divide_by_zero -> 除零异常 parse_error -> 格式异常 out_of_range -> 范围异常
The ordering of on and catch is important: Dart matches exception types in top-to-bottom order. More specific exception types should be placed first, and more general ones placed last. If catch (e) is placed at the beginning, the subsequent on clauses will never be executed.
rethrow: Rethrow the exception
Sometimes you need to do some processing after catching an exception (such as logging), and then let the exception continue to propagate upward.
The rethrow keyword is used to rethrow the currently caught exception, preserving the original stack information.
Example
void databaseOperation() {
print(' Connecting to database...');
throw Exception('EXAMPLE database connection failed');
}
void serviceLayer() {
try {
print(' Service layer: calling database operation');
databaseOperation();
} catch (e) {
// Log first, then rethrow
print(' [Log] database operation exception: $e');
rethrow; // Preserve the original exception and stack, continue to propagate upward
}
}
void controllerLayer() {
try {
print('Controller layer: handling request');
serviceLayer();
} catch (e, stackTrace) {
// Outermost layer catches and handles
print('Controller layer caught an exception');
print('Error message: $e');
print('Return to user: "Service temporarily unavailable, please try again later"');
}
}
void main() {
controllerLayer();
print('\n--- Difference between rethrow and throw e ---');
// Demonstrate the difference between rethrow and throw e
try {
try {
throw FormatException('Original exception');
} catch (e) {
// throw e: resets the stack trace, losing the original call location
print('Using throw e (loses original stack trace)...');
throw e; // Stack trace starts from here
}
} catch (e, stack) {
print('Caught: $e');
print('Stack trace points here (not the original location):\n$stack');
}
}
控制器层: 处理请求 服务层: 调用数据库操作 正在连接数据库... [日志] 数据库操作异常: Exception: EXAMPLE 数据库连接失败 控制器层捕获到异常 错误信息: Exception: EXAMPLE 数据库连接失败 给用户返回: "服务暂时不可用,请稍后重试" --- rethrow vs throw e 的区别 --- 使用 throw e(丢失原始堆栈)... 捕获: FormatException: 原始异常 堆栈指向这里(不是原始位置): ...
The difference between rethrow and throw e is crucial: rethrow preserves the full stack trace of the original exception, making it easier to locate the root cause; throw e resets the stack and starts over from the current position. When you need to continue propagating the exception after logging, use rethrow.
Best practices for exception handling
| Practice | Description |
|---|---|
| Only catch exceptions that you can handle. | Don't blindly catch all exceptions just to "avoid errors". If you don't know how to handle an exception, let it propagate upward. |
| Catch by type | Use on to specify the exception type, avoiding a single catch that swallows all exceptions. |
| finally: Clean up resources | Resources such as files and network connections should be closed in finally. |
| Don't swallow exceptions | Catching an exception and doing nothing (empty catch block) is dangerous; at least log it. |
| Use rethrow | When you need to log and continue propagating the exception, use rethrow instead of throw e. |
| Exception messages should be specific | throw Exception('Database connection failed: 192.168.1.1:3306') is more useful than throw Exception('An error occurred') |