前言
本文章受众需要掌握一定dart语言开发能力。
本文不对dart的错误处理Error handling/并发机制Asynchrony support做介绍。当在对dart的同步/异步机制有一定学习认识之后,这个文章会帮助你避开实践过程中容易踩的坑,真正掌握dart异常处理机制。
正文采用分-总结构,以提出问题-分析实践-得出结论的层次阐述。
如果想直接看结论,可以跳到最后一节“总结”。
测试代码
void main() async {
// await workerManager.init();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
@override
void initState() {
super.initState();
}
void _incrementCounter() {
setState(() {
_counter++;
/// 多层级捕获
tryErrorTopLevelCatch();
});
}
/// 顶层捕获
void tryErrorTopLevelCatch() {
try {
middleCatchError();
} catch (e) {
log('tryErrorTopLevelCatch: $e');
}
}
/// 中间层捕获
void middleCatchError() async {
try {
await asyncThrowError();
// throwError();
} catch (e) {
log('middleCatchError: $e');
}
}
/// 异步函数
Future<void> asyncThrowError() async {
throw 'throw err';
}
/// 同步函数
void throwError() {
throw 'throw err';
}
}
void log(String str) {
// 获取当前时间
var now = DateTime.now();
print('[$now] $str');
}
重点在_incrementCounter方法中
void _incrementCounter() {
setState(() {
_counter++;
tryErrorTopLevelCatch();
});
}
通过点击悬浮+按钮,调用tryErrorTopLevelCatch(),过程中,我们不断调整middleCatchError,asyncThrowError,throwError的方法实现,才达到我们测试的目的并得出结论。
错误场景
1. dart中try-catch是否支持捕获多层级调用栈异常?
改动如下
/// 顶层捕获
void tryErrorTopLevelCatch() {
try {
middleCatchError();
} catch (e) {
log('tryErrorTopLevelCatch: $e');
}
}
/// 中间层捕获
void middleCatchError() {
throwError();
}
/// 同步函数
void throwError() {
throw 'throw err';
}
输出:tryErrorTopLevelCatch: throw err
结论:异常未在当前函数域捕获时,会一层层抛到上层调用栈。通过try-catch可以捕获处理异常
2. 并发场景下,try-catch是否能捕获处理异常
修改代码如下
/// 中间层捕获,增加了async标识该函数为异步执行
Future<void> middleCatchError() async {
throwError();
}
结果:程序抛出异常
将tryErrorTopLevelCatch实现改动如下
void tryErrorTopLevelCatch() async {
try {
await middleCatchError();
} catch (e) {
log('tryErrorTopLevelCatch: $e');
}
}
输出:tryErrorTopLevelCatch: throw err
结论:try catch机制只能捕获同步函数执行异常,对于异步函数,需要增加await关键字,将函数调用方式更改为等同同步执行,才能正确捕获。
那么,如果不增加await,采用Future的catchError机制能否正确捕获异常呢?
将顶层tryErrorTopLevelCatch实现改动如下
/// 顶层捕获
void tryErrorTopLevelCatch() {
middleCatchError().catchError((err) {
log('async tryErrorTopLevelCatch: $e');
});
}
结果:程序抛出异常
将throwError改成异步执行。
Future<void> middleCatchError() async {
asyncThrowError();
}
/// 异步函数
Future<void> asyncThrowError() async {
throw 'throw err';
}
结果:程序抛出异常
中间异步函数增加await,是否能捕获?
Future<void> middleCatchError() async {
await asyncThrowError();
}
结果:程序抛出异常
只有在当前发生异常函数的调用处使用catchError捕获异常时才能成功
Future<void> middleCatchError() {
asyncThrowError().catchError((e) {
log('catchError: $e');
});
输出:catchError: throw err
结论:Future的catchError无法处理跨栈异常,无论是同步的还是异步的。捕获跨栈的异常,统一采用try-catch,需要注意的是针对异步函数需使用await转成同步语法。
3. catchError和onError同时存在时,会由哪个优先处理
改动如下
void middleCatchError() async {
asyncThrowError()
.onError((error, stackTrace) => log('onError: $error'))
.catchError((e) => log('catchError: $e'));
}
Future<void> asyncThrowError() async {
throw 'throw err';
}
输出:onError: throw err
变更下顺序
void middleCatchError() async {
asyncThrowError()
.catchError((e) => log('catchError: $e'))
.onError((error, stackTrace) => log('onError: $error'));
}
Future<void> asyncThrowError() async {
throw 'throw err';
}
输出:catchError: throw err
结论:可知,catchError和onError同时存在时,谁紧跟在调用方法后,谁处理。
4. try-catch和Future的onError/catchError同时存在时,由谁处理异常
void middleCatchError() async {
try {
asyncThrowError()
.onError((error, stackTrace) => log('onError: $error'))
.catchError((e) => log('catchError: $e'));
} catch (e) {
log('middleCatchError: $e');
}
}
Future<void> asyncThrowError() async {
throw 'throw err';
}
输出:onError: throw err
同时使用await修饰时
void middleCatchError() async {
try {
await asyncThrowError()
.onError((error, stackTrace) => log('onError: $error'))
.catchError((e) {
log('catchError: $e');
};
} catch (e) {
log('middleCatchError: $e');
}
}
Future<void> asyncThrowError() async {
throw 'throw err';
}
输出:onError: throw err
当没有实现Future的异常处理块onError/catchError时
void middleCatchError() async {
try {
await asyncThrowError();
};
} catch (e) {
log('middleCatchError: $e');
}
}
Future<void> asyncThrowError() async {
throw 'throw err';
}
输出:middleCatchError: throw err
结论:使用await可以捕获异步函数同步执行抛出的异常。try-catch无论有无await修饰,当和catchError/onError同时存在时,catchError/onError优先处理异常
5. 异步函数中,执行了耗时任务时,耗时任务发生异常,能否正确被上层捕获。
修改如下
Future<void> asyncThrowError() async {
// throw 'throw err';
Future.delayed(const Duration(seconds: 1), () {
throw 'throw err';
});
}
结果:异常未正确捕获。
修改成
Future<void> asyncThrowError() async {
Future.delayed(const Duration(seconds: 1), () {
throw 'throw err';
}).catchError((e) {
log('throwError: $e');
throw e;
});
}
输出:throwError:throw err
之后程序异常中断。
分析: 1.能正确输出,说明耗时任务抛出的异常被处理了。在这个场景中,异步任务中执行耗时任务,耗时任务抛出异常时,其实耗时任务块可以拆出写成函数形式,那么其实该问题跟上面问题2一样了,即对于当前调用的异步函数,需要对会执行异常的耗时任务增加catchError/onError块处理才行。
2.最后程序异常中断,说明catchError/onError块中抛出的异常,无法被上层try-catch或者Future的onError/catchError异常处理捕获到。原因目前未知。所以我们日常开发中要确保catchError/onError中不会执行抛出异常。
总结
1.同步函数的执行异常,可以通过try-catch跨多层捕获,前提是中间层函数都是同步调用的。
2.异步函数的执行异常,可以被当前执行函数上级调用栈通过Future的catchError/onError捕获处理。也可以将该函数通过await改成同步调用,这时可以认为变成了总结1中的同步函数执行情景,可以通过try-catch块进行跨多层调用栈捕获
3.try-catch无论有无await修饰,当和catchError/onError同时存在时,catchError/onError优先处理异常。而catchError和onError同时存在时,异常由catchError还是onError处理,只跟调用顺序有关,谁紧跟在调用方法后,谁处理。