「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」
今天这篇文章我们来介绍一下Dart
中的异步编程;
我们先来看一段代码:
String _string = '默认';
void main() {
getData();
print('其他业务');
}
getData() {
print('开始');
for (int i = 0; i < 10000000000; i++) {
_string = '耗时操作';
}
print('结束:$_string');
}
复制代码
其执行打印结果如下:
在这段代码中,for
循环的耗时操作卡着了后续代码的执行;
那么如何想for
循环变成异步操作呢?这个时候我们需要用到Future
;
Future
Future
与JavaScript
中的Promise
非常相似,表示一个异步操作的最终完成及其结果的表示。简单来说Future
就是用来处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future
只会对应一个结果,要么成功,要么失败;
需要注意的是,Future
的所有API
的返回值仍然是一个Future
对象,所以我们可以很方便的进行链式调用;
我们将代码修改如下:
String _string = '默认';
void main() {
getData();
print('其他业务');
}
getData() {
print('开始:$_string');
Future(() {
for (int i = 0; i < 10000000000; i++) {
_string = '耗时操作';
}
print('结束:$_string');
});
}
复制代码
运行结果:
通过运行结果我们发现,将for
循环放进Future
中之后,耗时操作将不会阻塞代码的执行;
await/async
在Flutter
中还有一种方式可以让我们像写同步代码一样来执行异步任务而不用使用回调的方式,这就是async/await
了,我们来看一段代码:
String _string = '默认';
void main() {
getData();
print('其他业务');
}
getData() async {
print('开始:$_string');
await Future(() {
for (int i = 0; i < 1000000000; i++) {
_string = '网络数据';
}
print('获取到数据:$_string');
});
print('结束:$_string');
}
复制代码
在此段代码中getData
方法使用了async
标识,而在方法内部for
循环所在的Future
添加了await
标识,那么会有什么效果呢?
可以看到,Future
之外的代码,等到Future
中代码执行完毕之后才继续执行了;
那么,sync
和await
都是什么作用呢?
async
用来表示函数是异步的,定义的函数会返回一个Future
对象,可以使用then
方法添加回调函数;await
后面是一个Future
,表示等待该异步任务完成,异步完成后才会继续往下执行;await
必须出现在async
函数内部;
可以看到,我们通过使用async
和await
将一个异步流调用使用同步的代码逻辑表示了出来;
async
和await
只是一个语法糖,编译器或者解释器最终都会将其转化为一个Future
的调用链;
then
那么,如果不使用await
,我们能否实现同样的效果呢?
Future
方法的返回值依然是一个Future
对象,我们使用Future
对象接收此返回值;然后通过future.then
来打印一下返回的数据: 我们将getData
方法修改如下:
getData() async {
print('开始:$_string');
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {
_string = '网络数据';
}
print('获取到数据:$_string');
});
future.then((value) {
print('then方法: $_string');
});
print('结束:$_string');
}
复制代码
我们看一下打印结果:
我们可以看到,虽然没有了await
标识,但是Future
对象的then
方法中依然可以获取到异步任务的数据;
但是此时then
方法中的value
却是空的:
这是因为,Future
中捕获的异步任务没有返回值,那么我们给这个异步任务添加一个返回值,我们将getData
方法修改如下:
getData() async {
print('开始:$_string');
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {
_string = '网络数据';
}
print('获取到数据:$_string');
return '返回数据';
});
future.then((value) {
print('then方法: $_string, value: $value');
});
print('结束:$_string');
}
复制代码
我们再来看一下打印结果:
此时,then
方法中返回的value
已经有值了,值为异步任务中return
的返回值;在Future
中返回的数据会被包装进Future
的对象中,然后返回一个Future
的对象;
catchError
很多情况下,我们在进行网络处理的时候的时候,会抛出异常,那么在Future
中应该如何处理异常呢?
我们来看下边代码:
getData() async {
print('开始:$_string');
Future future = Future(() {
for (int i = 0; i < 1000000000; i++) {
_string = '网络数据';
}
throw Exception('网络异常');
});
future.then((value) {
print('then方法: $_string, value: $value');
});
print('结束:$_string');
}
复制代码
在Future
内部,通过throw
抛出了一个Exception
,我们来看一下代码的执行结果:
可以看到,执行过程中代码报错,抛出了网络异常
的错误信息;那么我们就需要进行拦截,将Exception
捕获到,不能在执行中让工程出错,这个时候我们就需要使用catchError
捕获异常,我们将代码修改如下:
运行结果:
我们确实捕获到了异常,但是工程依然报错了,这是为什么呢?这个时候,我们需要用到链式调用,在then
方法之后,直接使用链式调用来捕获异常,代码如下:
运行结果:
onError
then
的定义如下:
Future<R> then<R>(FutureOr<R> onValue(T value), {Function? onError});
复制代码
我们发现,在then
方法中还有一个onError
方法,此方法也可以用来处理错误以及异常:
运行结果:
可以看到,使用then
中的onError
方法捕获错误之后,catchError
将不会调用;
此处的
onError
方法是在then
内部定义的,所以需要在then
方法内部调用,而非平级;
catchError
是在整个Future
的链式调用过程中捕获异常,而onError
只在当前then
中处理;我们在一个链式调用的过程中可能存在多个then
;
我们将catchError
与then
方法交换顺序:
运行结果:
我们发现,如果catchError
在then
方法前面,即使在catchError
中捕获了异常,那么then
方法依然会执行;
whenComplete
有时候,我们的异步任务无论成功与失败,都需要去做一些其他事情的时候,有两种方式处理*
- 在
then
或者catchError
中处理; - 使用
Future
的whenComplete
回调处理;
我们将代码修改如下:
运行结果:
虽然我们的whenComplete
完成状态执行了,但是异常依然被抛出了;这里有两种方式处理:
whenComplete
使用链式调用方式,不使用Future
的对象调用;- 在
whenComplete
之后,再次捕获异常,代码如下:
我们一般使用过程中将then
方法中放在前面,这样当异常捕获时,then
方法将不会执行:
在项目中使用时,我们推荐以下写法:
推荐将各个状态的逻辑处理方法抽出,如下: