异常的捕获
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
代表不需要导入的内容,我们可以根据需要进行指定。