Dart 中的异步编程

Dart 中的异步

最近在写 Flutter,结果发现 Dart 的异步编程还和我目前学过的都不太一样,简单总结一下。

Future

什么是 Future

Future 就是一个 Future<T> 对象,代表一个异步操作返回的结果,类型是 T。
如果返回的是不可用的值,则应该为 Future<void>

举个例子:

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

void main() {
print('test1:${DateTime.now()}');
testDelay();
print('test2:${DateTime.now()}');
}

testDelay() {

Future<String> f = testFuture();
f.then((str){
print(str);
});
print('testDelay:${DateTime.now()}');
}

Future<String> testFuture() {
// 延迟3秒
return Future.delayed(Duration(seconds: 3),() {
return 'future:${DateTime.now()}';
});
}

输出结果

1
2
3
4
5
test1:2019-02-03 03:03:41.206838
testDelay:2019-02-03 03:03:41.226762
test2:2019-02-03 03:03:41.227339
三秒后...
future:2019-02-03 03:03:44.227833

这就是 Future 的用法,其中用到了 then,如果需要使用链式编程,可以这样:

1
2
3
4
expensiveA()
.then((aValue) => expensiveB())
.then((bValue) => expensiveC())
.then((cValue) => doSomethingWith(cValue));

async / await

Dart 在 1.9 后,新加了两个关键字,asyncawait,即使不用 Future 也能让异步编程看起来就像同步编程一样。

上面的例子改成使用 asyncawait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
print('test111:${DateTime.now()}');
testDelay();
print('test2${DateTime.now()}');
}

testDelay() async {
print('testDelay:${DateTime.now()}');
String str = await testFuture();
print(str);
}

Future<String> testFuture() {
return Future.delayed(Duration(seconds: 3),() {
DateTime time = DateTime.now();
return 'future:$time';
});
}

同样的输出效果。
就相当于直接从 Future 获取到了值。这样与原来不同的是,在 await 执行后的代码都会被延后执行,也可以使用 await 来保证执行顺序。

1
2
3
4
5
main() async {
await expensiveA();
await expensiveB();
doSomethingWith(await expensiveC());
}

保证了执行顺序是 A -> B -> C。

Dart 中的异步编程除了 Future 还有 Stream,接下来我们看一下 StreamStream 在函数式编程中非常常见。

Streams

什么是 Stream

一个 Stream 就是数据的一个异步序列(数据流),比如用户生成的事件或者从文件读取的数据。

怎么生成一个 Stream 呢,我们来看一下 yield 关键字。

yield

yield 在异步编程会生成一个 Stream,需要用 async* 来标识。yield 的功能可以理解成一个叠加器,会不断累积前面的结果,加入新的数据,最后返回一个包含所有结果的容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stream<int> testYield() async* {
for (int i = 0; i < 15;++i) {
yield i;
}
}

// 要使用 `await for` 必须要在异步函数中
test() async {
Stream<int> st = testYield();
// 使用 await for 遍历 Stream 的内容
await for (int item in st) {
print(item);
}
}

如果 yield 在同步方法中使用会怎么样?那生成的就不是 Stream,而是一个 Iterable 对象。

Iterable

使用 yield 必须用 sync* 来标识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test() {
Iterable it = testYield();
it.forEach(print);

// 也可以用for来遍历
// for (var item in it) {
// print(item);
// }
}

Iterable testYield() sync* {
for (int i = 0; i < 15;++i) {
yield i;
}
}

yield*

如果我们要使用递归,对 yield 的结果进行 yield 会怎么样?看这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
test();
}

test() {
Iterable it = testYield(10);
it.forEach(print);
}

testYield(int n) sync* {
if (n > 0) {
yield n;
yield testYield(n - 1);
}
}

输出结果

1
(9, (8, (7, (6, (5, (4, (3, (2, (1, ())))))))))

这并不是我们想要的结果。看来对 yield 的结果进行 yield,则是直接把 yield 生成的容器进行了 yield。我们需要把 yield 生成容器的结果进行累加,则需要使用 yield*

修改为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main() {
test();
}

test() {
Iterable it = testYield(10);
it.forEach(print);
}

testYield(int n) sync* {
if (n > 0) {
yield n;
yield* testYield(n - 1);
}
}

这样就能达到我们想要的结果了。

参考

Dart 官方文档