关于 Flutter 的异步编程 1、Future 的运行规则

入坑 Flutter 后,第一个接触到的需求就需要网络请求。因为之前都是接触 Android 原生开发,所以很自然的就想到了需要多线程编程,于是学习了一下 Flutter 的异步编程。

其实说的是 Dart 的异步编程,因为 Flutter 使用的是 Dart 语言。

怎么说呢,如果之前只写过 JAVA 的多线程编程,那么可能在 Flutter 这一块就有点理解困难。当然如果你之前写过 JavaScript ,那么你应该可以很快地学会 Flutter 的异步,因为两者差不多。直接开始吧。

这是第一章,主要分析 Future 对象的运行规则。还有很多技巧等到下一节吧。

异步运行

直接看以下代码:

1
2
3
4
5
6
7
8
9
main(){

Future future = Future((){
print('future init');
});

print("Hello World !");

}

结果:

可以看到, future init 要比 Hello World ! 慢输出。实际上,以上代码可以类比于 Java 的以下代码:

1
2
3
4
5
6
7
static void main(string[] args){
new Thread(()->{
System.out.printIn("future init");
}).start();

System.out.printIn("Hello World !");
}

要注意的是,这里虽然能类比,但是真实情况不太一样,Java 中的操作是新建了一个新的线程并和原来主线程同时运行。

因为 Dart 是单线程模型,所以没有多线程之分。这也是我之前称这种为异步编程而不是多线程编程的原因。

这种类比方式只是让你能快速理解 Dart 中的异步,实际上两者本质是不一样的。

这里也不打算深究 Future 异步的实现方式,直接知道怎么用即可。

Future

Future 对象拥有三个常用的方法,分别是 thencatchErrorwhenComplete 方法,这里分别来看。

这三个方法操作后都返回 future 对象本身,也就是都支持链式调用,之后就不再讲这一点了。

then

then 方法有两个参数,其中一个为必选一个为命名。

其中必选参数为有一个参数的方法,命名参数 onError 我之后再讲。

还是直接看以下代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
main(){

Future future = Future((){
print('future init');
return 'then 1 from init';
})

.then((value){
print('then 1 -> $value');
return 'then 2 from then 1';
})

.then((value) {
print('then 2 -> $value');
});


print("Hello World !");

}

结果:

应该可以很容易看出来,then 可以说是为了代码可读性而推出的,当你调用第一个 then 的时候,参数的回调里的参数将是这个 future 对象构造参数的返回值,此后每一个 then 都是上一个 then 的返回值。同时,这一整个 future 有关的操作和都是异步运行的。

whenComplete

whenComplete 也是很好理解,有点类似于 try-catch 模型中的 finally ,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
main(){

Future future = Future((){
print('future init');
return 'then 1 from init';
})

.then((value){
print('then 1 -> $value');
throw NullThrownError();
return 'then 2 from then 1';
})

.then((value) {
print('then 2 -> $value');
})

.whenComplete(() => print('complete'));



print("Hello World !");

}

结果:

我们在 第一个 then 后手动抛出一个异常,然后第二个 then 就不会被执行。也就是抛出异常的 then 之后的 then 都不会被执行,但是 whenComplete 无论是否抛出异常都会被执行。

注意:whenComplete 在没抛出异常的情况下和普通的 then 一样,也就是说如果你在 whenComplete 之后调用 then ,这个 then 也会在 whenComplete 之后执行。

catchError

catchError 如果之前的 then 发生了异常,则会调用该方法,如果没有异常则不会。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
main(){

Future future = Future((){
print('future init');
return 'then 1 from init';
})

.then((value){
print('then 1 -> $value');
throw NullThrownError();
return 'then 2 from then 1';
})

.then((value) {
print('then 2 -> $value');
})

.whenComplete(() => print('complete'))

.catchError((error){
print(error);
});

print("Hello World !");

}

结果:

可以看到我们之前抛出的异常被 catchError 捕获。如果没有异常的话,catchError 不会被执行。

异常处理

then 抛出异常后,后面的所有 then 都不被执行。

有两个方法可以处理异常

1、catchError

例如以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
main(){

Future future = Future((){
print('future init');
return 'then 1 from init';
})

.then((value){
print('then 1 -> $value');
throw NullThrownError();
return 'then 2 from then 1';
})

.then((value) {
print('then 2 -> $value');
return 'then 3 from then 2';
})

.catchError((error){
print('on catchError :$error');
})

.then((value) => print('then 4 -> $value'))

.whenComplete(() => print('complete'));

print("Hello World !");

}

结果:

可以看到,我们在 then1 里抛出了一个异常,然后后面紧接着的 then2 并没有被执行,然后我们调用 catchError 将该异常捕获掉,这样后面的 then4 就可以执行了,不过因为 then1return 在抛异常之后,所以这里的参数为 null

2、 onError

还记得我们的 then 的第二个命名参数吗?这个命名参数的名字为 onError ,并且这个参数的方法模型和 catchError 参数的方法模型是一样的。

这个参数是用来捕获上一个 then 产生的异常。没错上一个。

观察以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
main(){

Future future = Future((){
print('future init');
return 'then 1 from init';
})

.then((value){
print('then 1 -> $value');
throw NullThrownError();
return 'then 2 from then 1';
},
onError: (error)=>{print('then 1 onError -> $error')})

.then((value) {
print('then 2 ');
return 'then 3 from then 2';
},
onError: (error){print('then 2 onError -> $error');})

.catchError((error){
print('on catchError :$error');
})

.then((value) => print('then 3 -> $value'))

.whenComplete(() => print('complete'));

print("Hello World !");

}

结果:

可以看到,我们在 then1 里也传入 onError,同时 then1 中抛出异常,但是 then1onError 并没有执行,反而是 then2onError 执行了。同时因为异常已经被捕获了,所以后面的 catchError 并没有被执行。

而因为异常已经被捕获了,所以 then3 也成功的执行,只不过参数丢失。

运行规则

同一个 Future 里,无论是 thencatchError 还是 whenComplete,它的运行顺序都是从上到下的。并且按照以下规则:

1
2
3
遇到 then ,传入上一个 then 或者构造方法的返回值
遇到 whenComplete ,直接执行(不会等到所有 then 都执行完毕)
遇到 catchError , 判断之前知否有异常,有就执行并捕获,没有就不执行
1
2
3
4
5
6
7
8
9
当 then 抛出异常的时候,从下一个 then 开始,直到 then 被捕获,否则所有的 `then` 都不会被执行

如果在 whenComplete 或者 catchError 里抛出异常,之后的处理和从 then 里抛出异常的处理方法一致

捕获异常有两种方法,catchError 和 then 的命名参数 onError

捕获异常后,之后的 then 参数为 null

注:then 的命名参数 onError 是捕获之前的异常,其实相当于在 then 之后补了一个 catchError 的语法糖而已。

运行顺序图解:

modelthencatchErrorwhenComplete 三种代码块。

每一个 Model 都会将一个参数或者是异常传给下一个 Model,不同 Model 的处理方式不一样

具体规则如下:

then:

whenComplete:

PS:这里图写错了,没发生异常的话是传入上一个 Model 传入的东西。可能是异常也可能是参数。

catchError:

后记

这就是 Future 的运行顺序相关的内容了。这是 Flutter 异步编程的第一个内容,其实在真正使用中还有很多其他的技巧。具体就等下一章吧。