跳转到正文
YGJoon 技术博客

Dart 错误处理详解

发布于:
·1.4k 字 · 约 3 分钟·作者:
try-catch-finally 的捕获姿势、自定义异常的设计,再到 Future 与 Stream 里异步错误的传播与兜底,异常处理全链路。
Dart 错误处理详解文章封面
目录

异常基础

抛出异常

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}');
  }
}

最佳实践

  1. 只捕获预期的异常
  2. 提供有意义的错误信息
  3. 合理使用 rethrow
  4. 实现全局错误处理
  5. 记录错误日志

注意事项

  1. 避免捕获过于宽泛的异常
  2. 不要忽略异常
  3. 合理使用 finally 块
  4. 注意异步错误处理
  5. 保持错误处理的一致性

实战案例:工具类里的错误处理哲学

我的 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 的我自己。写错误处理时先问一句”这个错误最终给谁看”,答案会直接告诉你该怎么写。


分享这篇文章:


相关文章

上一篇
Dart 集合类型详解
下一篇
Dart 库和可见性详解