这是我参与11月更文挑战的第9天,活动详情查看:2021最后一次更文挑战
异常的捕获
onError 与 catchError 的区别
getdata() async {
Future future = Future(() {
for (int i = 0; i < 10000000; i++) {}
throw Exception('网络异常');
});
// future.then((value) => print('value=$value'),
// onError: (e)=>print(e.toString()));
future
.then((value) => print('value=$value'))
.catchError((e)=>print('使用的时候捕获到了异常:' + e.toString()));
}
当我们在 Future 的闭包中抛出异常的时候可以使用 onError 或者 catchError 进行捕获异常并处理,它们的区别就是 onError 是写在 then 里面的,是针对 then 这个事件。
catchError 的使用顺序
虽然我们上面已经通过 catchError 捕获了异常,但是当我们后面再调用 whenComplete 的时候还需要再次调用 catchError,要不然程序还是会抛出异常。正确代码如下。
future
.then((value) => print('value=$value'))
.catchError((e)=>print('使用的时候捕获到了异常:' + e.toString()));
future
.whenComplete(() => print('完成了'))
.catchError((e)=>print('完成的时候捕获到了异常:' + e.toString()));
Future 链式调用
Future(() {
for (int i = 0; i < 10000000; i++) {}
throw Exception('网络异常');
})
.then((value) => print('value=$value'))
.whenComplete(() => print('完成了'))
.catchError((e)=>print('捕获到了异常:' + e.toString()));
针对以上代码我们我们可以使用链式调用进行简写,但是需要注意一点的是 catchError 最好放到最后面。
代码抽取
getdata() async {
Future(() {
for (int i = 0; i < 10000000; i++) {}
throw Exception('网络异常');
})
.then(thenFunc)
.whenComplete(() => print('完成了'))
.catchError((e)=>print('捕获到了异常:' + e.toString()));
}
FutureOr thenFunc(Never value) {
print('value=$value');
}
以 then 为例,当我们闭包中代码量比较多的时候,我们可以在外面定义一个方法,对代码进行抽离。
多个异步任务的处理
Future 任务顺序执行
testFuture() async {
Future(() {
return '任务 1';
}).then((value) => print('value=$value'));
Future(() {
sleep(Duration(seconds: 1));
return '任务 2';
}).then((value) => print('value=$value'));
Future(() {
return '任务 3';
}).then((value) => print('value=$value'));
}
这里因为 Future 任务都是在同一个队列中,且 Flutter 是单线程,所以这里任务的执行顺序是按照 Future 的添加顺序执行的,任务1 -> 任务2 -> 任务3。
任务前后依赖
后面的任务依赖前一个任务的结果
Future(() {
return '任务 1';
}).then((value) {
print('$value结束');
return '任务 2';
}).then((value) {
print('$value结束');
return '任务 3';
});
当我们后面的任务需要依赖前一个任务的执行结果的时候,可以在 then 的闭包中执行下一个任务,并把结果数据返回出去。
多个任务结束统一处理
Future.wait([
Future(() {
return '任务 1';
}),
Future(() {
return '任务 2';
}),
Future(() {
return '任务 3';
})
]).then((value) => print(value[0] + value[1] + value[2]));
当我们碰到这样一个需求,同时请求多个接口,在这些接口都请求完成的时候统一处理,这时候我们就可以使用 Future.wait,Future.wait 里面是一个数组,可以放入多个 Future 任务,在这些任务都执行完毕的时候会调用 then 方法,这时候 value 是一个数组,装的是这几个 Future 任务的返回结果。这里 value 数组的顺序跟 Future 任务的顺序是一样的,且 Future 任务也是顺序执行的。
Dart 事件循环
通过上图案例我们可以看到 scheduleMicrotask 中的任务会比 Future 中的任务先执行,这里是因为在 Dart 中有两种队列,事件队列跟微任务队列。
- 事件队列(
event queue),包含所有的外来事件:I/O、mouse events、drawing events、timers、isolate之间的信息传递。 - 微任务队列(
microtask queue),表示一个短时间内就会完成的异步任务。它的优先级最高,高于event queue,只要队列中还有任务,就可以一直霸占着事件循环。microtask queue添加的任务主要是由Dart内部产生。
因为
microtask queue的优先级高于event queue,所以如果microtask queue有太多的微任务, 那么就可能会霸占住当前的event loop。从而对event queue中的触摸、绘制等外部事件造成阻塞卡顿。
在每一次事件循环中,Dart 总是先去 microtask queue 中查询是否有可执行的任务,如果没有,才会处理后续的 event queue 中的任务。
相关案例
- 案例 1
Future f = Future(() => print('1'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
f.then((value) => print('4'));
print('5');
这里打印的顺序是 5、3、1、4、2。
- 案例 2
Future f = Future(() => print('1'));
Future(() => print('2'));
scheduleMicrotask(() => print('3'));
f.then((value) {
print('4');
scheduleMicrotask(() => print('5'));
}).then((value) => print('6'));
print('7');
这里打印的顺序是 7、3、1、4、6、5、2,这里 6 相当于 f.then 里面的任务, then 中的任务相当于被添加到了微任务队列,所以 4、6 在 5 之前执行。
Dart 中的多线程 Isolate
Isolate.spawn(func1, 10);
Isolate.spawn(func2, 20);
Isolate.spawn(func3, 30);
如上代码中 func1 、func2、func3 会在子线程中执行,且是无序的。Dart 中 Isolate 更像是一个进程,它有独立的内存空间,也就意味着每个进程中的空间是独立的,所以不存在资源抢夺的问题,所以不需要锁,这样的话用起来就非常便捷。但是也有一些需要注意的问题,数据不能直接访问,下面我们来看一下。
这里我们在 func 中对 a 的值进行了修改,但是第二次打印的时候可以看到 a 的值还是 10。因为 func 中的 a 被独立起来了,与外部相互之间不能共享。如果想让 func 中 a 的值的修改能在外部起作用的话就需要用到端口。
int a = 10;
IsolateDemo() async {
//创建 port
ReceivePort port = ReceivePort();
//创建 Isolate
Isolate iso = await Isolate.spawn(func, port.sendPort);
//通过 port 监听数据变化
port.listen((message) {
a = message;
print('接收到了:a = $a');
//关闭端口
port.close();
iso.kill();
});
}
func(SendPort send) {
send.send(100);
print('第一次打印:a = $a');
}
这里我们可以定义一个 port,在 func 中传入 port.sendPort,这时候在 func 方法中发送数据,在 listen 闭包中就可以监听到。端口使用完毕后要调用 close 关闭端口,调用 kill 销毁 Isolate。
computeDemo() async {
int a = await compute(func1, 10);
}
int func1(int count) {
return 100;
}
Dart 中使用多线程除了 Isolate,还有 compute,compute 是基于 Isolate 的封装,用法与 Isolate 类似,有区别的就是 compute 可以接收函数中的返回值。
扩展
pubspec.yaml 文件介绍
name:项目名称,必填字段。description:项目描述,非必填字段。publish_to:代表要发布的平台,none的话代表不发布。version:工程的版本号。dependencies:可以设置flutter的版本,默认是获取最新版本。environment:可以指定dart版本的兼容范围。dev_dependencies:代表开发环境下的指定版本,打包的时候不会被打包。flutter:字体以及图片都是在flutter下面设置。dio: ^4.0.1: 以dio三方库为例,^4.0.1代表大版本区间不变的写法,相当于>= 4.0.1, < 5.0.0。dio: 4.0.1代表指定版本,dio: any代表任意版本,dio:>3.0.1`` 代表版本号大于 3.0.1。
import 介绍
import 'package:http/http.dart' as http;
以 http 为例,当我们导入 import 的时候这里可以看到 as,as 的作用就是给库起别名,防止类名或者方法名冲突。导入库的时候默认是整个文件都导入,关键字 show 代表只要导入的内容,hide 代表不需要导入的内容,我们可以根据需要进行指定。