异常基础
抛出异常
void checkAge(int age) {
if (age < 0) {
throw ArgumentError('Age cannot be negative');
}
if (age > 120) {
throw RangeError('Invalid age value');
}
}
捕获异常
try {
checkAge(-5);
} on ArgumentError catch (e) {
print('Invalid argument: $e');
} on RangeError catch (e) {
print('Out of range: $e');
} catch (e) {
print('Unknown error: $e');
} finally {
print('Finished checking age');
}
常见异常类型
内置异常
// 参数错误
throw ArgumentError('Invalid argument');
// 范围错误
throw RangeError('Index out of range');
// 状态错误
throw StateError('Invalid operation');
// 格式错误
throw FormatException('Invalid format');
// 类型错误
throw TypeError();
自定义异常
class CustomException implements Exception {
final String message;
final DateTime timestamp;
CustomException(this.message) : timestamp = DateTime.now();
@override
String toString() => 'CustomException: $message (at $timestamp)';
}
void riskyOperation() {
throw CustomException('Something went wrong');
}
异步错误处理
Future 错误处理
Future<void> fetchData() async {
try {
final result = await someAsyncOperation();
processResult(result);
} catch (e) {
print('Error fetching data: $e');
rethrow; // 重新抛出异常
}
}
// 使用 catchError
fetchData()
.then((value) => print('Success'))
.catchError((error) => print('Error: $error'))
.whenComplete(() => print('Completed'));
Stream 错误处理
Stream<int> countStream() async* {
for (int i = 0; i < 10; i++) {
if (i == 5) {
throw Exception('Error at count 5');
}
yield i;
}
}
void handleStream() {
countStream().listen(
(data) => print('Data: $data'),
onError: (error) => print('Error: $error'),
onDone: () => print('Stream completed'),
);
}
完整示例
// 自定义异常定义(上文已讲过,这里补全以便直接运行)
class CustomException implements Exception {
final String message;
final DateTime timestamp;
CustomException(this.message) : timestamp = DateTime.now();
@override
String toString() => 'CustomException: $message (at $timestamp)';
}
class DataService {
// 模拟网络请求
Future<Map<String, dynamic>> fetchUserData(String userId) async {
try {
await Future.delayed(Duration(seconds: 1));
if (userId.isEmpty) {
throw ArgumentError('User ID cannot be empty');
}
if (userId == 'error') {
throw CustomException('Failed to fetch user data');
}
return {
'id': userId,
'name': 'John Doe',
'email': '[email protected]',
};
} on ArgumentError catch (e) {
print('Invalid argument: $e');
rethrow;
} on CustomException catch (e) {
print('Custom error: $e');
rethrow;
} catch (e) {
print('Unknown error: $e');
rethrow;
}
}
// 模拟数据流
Stream<int> dataStream() async* {
try {
for (int i = 0; i < 5; i++) {
if (i == 3) {
throw StateError('Error at count 3');
}
await Future.delayed(Duration(seconds: 1));
yield i;
}
} catch (e) {
print('Stream error: $e');
rethrow;
}
}
}
class ErrorHandler {
static void handleError(Object error, StackTrace stackTrace) {
print('Error occurred: $error');
print('Stack trace: $stackTrace');
// 可以在这里添加错误日志记录、错误报告等逻辑
}
static Future<T> wrap<T>(Future<T> Function() operation) async {
try {
return await operation();
} catch (e, stackTrace) {
handleError(e, stackTrace);
rethrow;
}
}
}
void main() async {
final service = DataService();
// 测试异步错误处理
try {
await ErrorHandler.wrap(() => service.fetchUserData(''));
} catch (e) {
print('Caught error: $e');
}
// 测试 Stream 错误处理
service.dataStream().listen(
(data) => print('Received: $data'),
onError: (error) => print('Stream error: $error'),
onDone: () => print('Stream completed'),
);
}
错误处理策略
1. 错误恢复
Future<String> fetchDataWithRetry() async {
for (int i = 0; i < 3; i++) {
try {
return await fetchData();
} catch (e) {
if (i == 2) rethrow;
await Future.delayed(Duration(seconds: 1));
}
}
throw StateError('Failed after 3 retries');
}
2. 错误转换
import 'dart:io'; // HttpException 在这里
Future<T> convertError<T>(Future<T> Function() operation) async {
try {
return await operation();
} on HttpException catch (e) {
throw CustomException('Network error: ${e.message}');
} on FormatException catch (e) {
throw CustomException('Format error: ${e.message}');
}
}
最佳实践
- 只捕获预期的异常
- 提供有意义的错误信息
- 合理使用 rethrow
- 实现全局错误处理
- 记录错误日志
注意事项
- 避免捕获过于宽泛的异常
- 不要忽略异常
- 合理使用 finally 块
- 注意异步错误处理
- 保持错误处理的一致性
实战案例:工具类里的错误处理哲学
我的 ygking_json_to_dart_model 是个 JSON 转 Dart 的小工具,它的错误处理跟本文教的”标准做法”完全不一样——而且是故意的:
import 'dart:convert';
String convertJsonToDart(String jsonStr, String className) {
try {
final dynamic jsonData = json.decode(jsonStr);
final buffer = StringBuffer();
// ... 生成逻辑(往 buffer 里拼接代码)
return buffer.toString();
} catch (e) {
return '// 错误: JSON格式无效\n// $e';
}
}
看到没?catch 里没有 rethrow,没有自定义异常类,而是把错误信息当成注释返回。为什么?因为这个工具的使用场景是:用户粘一段 JSON 进来,点”生成”,结果直接显示在代码预览区。如果 JSON 写错了,抛异常只会让 UI 崩掉;而返回一段 // 错误: ... 的注释,用户一眼就看到错在哪,还能直接复制去问人。
这个决策背后是个原则:错误处理的方式取决于”谁消费这个错误”。如果是给开发者看的库,抛异常是对的(调用方必须处理);如果是给终端用户看的工具,把错误翻译成用户能懂的信息更重要。我的工具用户就是我自己,// 错误: JSON格式无效 比一个堆栈跟踪有用得多。
当然,这种写法有个代价:调用方无法用 try-catch 区分”成功”和”失败”——返回的都是 String。所以我在文档里写明了:返回内容以 // 错误 开头即为失败。丑,但够用。工具类代码,实用优先。
总结
try-catch 的语法半小时能学会,难的是”怎么处理”这个决策——是抛给调用方、重试、还是翻译成用户能懂的话。我的 JSON 工具选了第三种:把错误拼成注释返回,因为它的”用户”就是粘 JSON 的我自己。写错误处理时先问一句”这个错误最终给谁看”,答案会直接告诉你该怎么写。
