【Flutter】Dart 异步编程
基于 Dart VM version: 2.1.0-dev.4.0.flutter-4eb879133a (Tue Sep 18 19:20:33 2018 +0000) on “macos_x64”
Dart 基础库提供了大量返回值类型为 Future
和 Stream
的函数,这些函数都是异步函数,当调用这些函数的时候,函数在完成一些“基础配置“后就会立马返回,而不是等待函数执行完成。
Future
在 JS 世界里,有 Promise
,Dart 中相应实现为 Future
。在语意上,Future
表示将来某个时间开始并完成的事情的结果,这和 Promise
含义相近。
下面是一个简单🌰:
1 | main(List<String> args) { |
所以可以知道 Future 在创建后立马返回,而不是执行里面的方法体。
异常处理
Future 异常处理有两种方式,一种是通过 then
函数的可选命名参数:onError
;第二种是通过 catchError
方法。不过更加完善的异常处理一般是两个方式配合使用,看🌰:
1 | main(List<String> args) { |
从结果上看,onError
和 catchError
都可以接受到之前发生的错误,但是这两个处理的使用场景还是有细微的 差别。
Stream
Stream
表示一系列数据或事件,比如点击时间和通知等。
这里直接看例子:
1 | main(List<String> args) { |
输出结果中,会先输出 “main”。
Stream
在调用 listen
方法之后会建立一个订阅关系,然后发送数据。
Stream
也分为两种:”Single-subscription” 和 “broadcast”,前者表示只能有一个监听者,即只能调用 listen
调用一次;后者表示一种多播模式,可以调用多次 listen
与多个监听者建立订阅关系。
async & await
前面 Future
在处理单次简单的异步任务时表现优异,但是当面对多次异步需求,比如两次网络请求,第二次请求依赖第一次请求的返回结果(当然这只是最简单的场景),就会出现类似回调地狱的问题。async
和 await
就是应对这样在问题语法层面的一种方式,它们使得异步调用变得像同步调用那样顺畅。
和 JS 一样,await 只能在 async 函数内部使用
async 函数
async 函数和普通函数的声明方式基本没差别,你只需要在方法体之前加上 async
关键字就OK 了,比如:
1 | [Future<int>] getInt() async{ |
除了增加了修饰符,也将返回值类型使用 Future
进行了包装,但是奇妙的是你并不需要自己将返回值进行包装,Dart 会帮你,如果你想自己手动包装也没问题。
await 表达式
我们先看看怎么调用 async 函数:
1 | main(List<String> args) { |
假如想要 “main” 在 “2333” 之后输出,那么可以改成这样:
1 | main(List<String> args) { |
问题得到了解决,但是面对更复杂的场景时,回调地狱还是会发生,这个时候再做做修改:
1 | main(List<String> args) async { |
可以看到 main 函数变成了 async 函数,同时第2行使用了 await
关键字。通过这样的改造,本来是异步的 getInt()
变成了同步的,整个调用流程看起来也更加流畅了。
实例代码
1 | Future<String> _getUserInfo() async { |
这里可以看到 async
函数会一直执行直到 await
或 return
,然后立即返回。
sync* & async* & yield & yield*
在学习 Future、async、await 之后,接着聊聊 Generator (毕竟是想要取代 JS 的语言)。
关于 Generator,可以理解成一系列等待计算的序列。Dart 通过 sync*、async* 等关键字帮助开发者快速实现自己的生成器。
这里我们可以将 async* 看作 async 的加强版,即你可以在 async* 函数里面使用 await
同步 Genrator
同步 generator 函数返回值是 Iterable
:
1 | Iterable naturalsTo(n) sync* { |
上面代码就实现了一个简单 Generator,调用 naturalsTo
后会得到一个 Iterable,但是方法体并不会立即执行。调用这个 Iterable 的 iterator 的 moveNext()
,方法体会执行到 yield
语句(包含该语句)为止,并能通过 iterator.current
拿到 yield 表达式的返回值。
异步 Genrator
异步 generator 函数返回值是 Stream
:
1 | Stream asynchronousNaturalsTo(n) async* { |
同样,调用 asynchronousNaturalsTo
立即得到一个 Stream
,方法体也是直到调用 listen
之后才会执行。当执行到 yield
时,计算得到的结果会推给 listener,同时继续执行止步到下一句 yield
之前。
实例代码
上面的描述还是比较抽象,看起来同步异步 Generator 似乎并没有差别,我们看下下面代码的运行结果来具体感受一下:
1 | main(List<String> args) async { |
当调用变成这样时:
1 | main(List<String> args) async { |
⚠️注意看 “main” 的输出位置,这样大概就能理解同步异步生成器的区别了。
递归调用
假如存在递归调用,则可以这样写:
1 | Iterable naturalsDownFrom(n) sync* { |
不过我们可以通过 yield*
简化上述代码:
1 | Iterable naturalsDownFrom(n) sync* { |
异步 Generator 同理
Reference
https://www.dartlang.org/guides/language/language-tour#asynchrony-support