Dart 异步编程生成器及自定义类型用法详解
作者:SaraiNoQ
异步支持
今天介绍一下 Dart 学习的最后一节内容,包括异步的使用、生成器语法以及类型别名的使用。
Dart 类库有非常多的返回Future
或者Stream
对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO 操作。而不是等到这个操作完成。
同时,async
和await
关键词支持了异步编程,允许您写出和同步代码很像的异步代码。
Future
Future
与 JavaScript 中的Promise
非常相似,表示一个异步操作的最终完成(或失败)及其结果值的表示。简单来说,它就是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作。一个Future 只会对应一个结果,要么成功,要么失败。
注: Future
的所有API的返回值仍然是一个Future
对象,所以可以很方便的进行链式调用。
Future.then
为了方便示例,在本例中使用Future.delayed
创建了一个延时任务(实际场景会是一个真正的耗时任务,比如一次网络请求),即2秒后返回结果字符串hi world!
,然后我们在then
中接收异步结果并打印结果,代码如下:
Future.delayed(new Duration(seconds: 2),(){ return "hi world!"; }).then((data){ print(data); });
Future.catchError
如果异步任务发生错误,可以在catchError
中捕获错误,将上面示例改为:
Future.delayed(new Duration(seconds: 2),(){ //return "hi world!"; throw AssertionError("Error"); }).then((data){ //执行成功会走到这里 print("success"); }).catchError((e){ //执行失败会走到这里 print(e); });
then
方法还有一个可选参数onError
,也可以它来捕获异常:
Future.delayed(new Duration(seconds: 2), () { //return "hi world!"; throw AssertionError("Error"); }).then((data) { print("success"); }, onError: (e) { print(e); });
Future.whenComplete
有些时候,我们会遇到无论异步任务执行成功或失败都需要做一些事的场景,比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。这种场景,有两种方法,第一种是分别在then
或catch
中关闭一下对话框,第二种就是使用Future
的whenComplete
回调,我们将上面示例改一下:
Future.delayed(new Duration(seconds: 2),(){ //return "hi world!"; throw AssertionError("Error"); }).then((data){ //执行成功会走到这里 print(data); }).catchError((e){ //执行失败会走到这里 print(e); }).whenComplete((){ //无论成功或失败都会走到这里 });
Future.wait
使用Future.wait
可以做到多个Future
同时出发才会进行后续操作,同 JavaScript 中的Promise.all()
方法。
Future.wait
接受一个Future
数组参数,只有数组中所有Future
都执行成功后,才会触发then
的成功回调,只要有一个Future
执行失败,就会触发错误回调。下面,我们通过模拟Future.delayed
来模拟两个数据获取的异步任务,等两个异步任务都执行成功时,将两个异步任务的结果拼接打印出来,代码如下:
Future.wait([ // 2秒后返回结果 Future.delayed(new Duration(seconds: 2), () { return "hello"; }), // 4秒后返回结果 Future.delayed(new Duration(seconds: 4), () { return " world"; }) ]).then((results){ print(results[0]+results[1]); }).catchError((e){ print(e); }); /* 最后会在4秒后拿到结果 */
更多 Future 的 api 请自行查询文档。
async 和 await
异步函数是函数体被用async
修饰符标记的函数。 向函数中添加async
关键字将使其返回一个 Future。
String lookUpVersion() => '1.0.0'; // 返回String
Future<String> lookUpVersion() async => '1.0.0'; // 返回Future<String>
然后我们可以使用await
关键字在内部直接接受一个 Future 的then
的成功回调,就如同 JavaScript 中的那样:
task() async { try{ String id = await login("alice","******"); String userInfo = await getUserInfo(id); await saveUserInfo(userInfo); // 执行接下来的操作 } catch(e){ // 错误处理 print(e); } }
async
用来表示函数是异步的,定义的函数会返回一个Future
对象,可以使用then方法添加回调函数。await
后面是一个Future
,表示等待该异步任务完成,异步完成后才会往下走。注意,await
必须出现在async
函数内部。
注意: 函数的主体不需要使用 Future 的 API。如果需要,Dart 将创建 Future
的对象。如果没有返回一个有用的值,那么将其返回Future<void>
类型。
处理流(Stream)
Stream
也是用于接收异步事件数据,和Future
不同的是,它可以接收多个异步操作的结果(成功或失败)。 也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream
常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。
当需要从 Stream 获取值时,有两个选择:
使用async
和异步的for
循环(await for
)
注: 在使用await for
之前,请确保它使代码更清晰,并且确实希望等待流(Stream)的所有结果。例如,通常不应该为 UI 事件使用await
,因为 UI 框架会发送无穷无尽的事件流。
异步for
循环有以下形式:
await for (varOrType identifier in expression) { // Executes each time the stream emits a value. }
表达式的值必须具有 Stream 类型。执行过程如下:
等待流发出值。
执行for
循环的主体,并将变量设置为发出的值。
重复1和2,直到流关闭。
要停止侦听流,可以使用break
或return
语句,该语句将跳出for
循环,并从流中取消订阅。
如果在实现异步for
循环时出现编译时错误,请确保await
在异步函数中。例如,要在应用程序的main()
函数中使用异步for
循环,main()
的主体必须标记为async
:
Future main() async { // ... await for (var request in requestServer) { handleRequest(request); } // ... }
使用Stream API,如[库的引导]中的描述
Stream.fromFutures([ // 1秒后返回结果 Future.delayed(new Duration(seconds: 1), () { return "hello 1"; }), // 抛出一个异常 Future.delayed(new Duration(seconds: 2),(){ throw AssertionError("Error"); }), // 3秒后返回结果 Future.delayed(new Duration(seconds: 3), () { return "hello 3"; }) ]).listen((data){ print(data); }, onError: (e){ print(e.message); }, onDone: (){ }); /* 上面的代码依次会输出: I/flutter (17666): hello 1 I/flutter (17666): Error I/flutter (17666): hello 3 */
生成器
当需要延迟地生成一个值序列时,请考虑使用生成器函数。
Dart 内置支持两种生成器函数:
- 同步生成器:返回 Iterable 对象
- 异步生成器:返回 Stream 对象
要实现同步生成器函数,将函数体标记为sync*
,并使用yield
语句传递值:
Iterable<int> naturalsTo(int n) sync* { int k = 0; while (k < n) yield k++; }
要实现异步生成器函数,将函数体标记为async*
,并使用yield
语句传递值:
Stream<int> asynchronousNaturalsTo(int n) async* { int k = 0; while (k < n) yield k++; }
如果生成器是递归的,可以使用yield*
来改进它的性能:
Iterable<int> naturalsDownFrom(int n) sync* { if (n > 0) { yield n; yield* naturalsDownFrom(n - 1); } }
类型定义
在 Dart 中,函数是对象,就像字符串和数字是对象一样。typedef
或function-type
为函数提供一个类型别名,可以在声明字段和返回类型时使用这个名称。当函数类型被分配给变量时,typedef
保留类型信息。
以下代码不使用typedef
:
class SortedCollection { Function compare; SortedCollection(int f(Object a, Object b)) { compare = f; } } // Initial, broken implementation. int sort(Object a, Object b) => 0; void main() { SortedCollection coll = SortedCollection(sort); // All we know is that compare is a function, // but what type of function? assert(coll.compare is Function); }
上面的代码中,当给compare
分配f时类型信息会丢失。f
的类型是(Object, Object)->int(int表示返回值类型)
,当然,compare
的类型是Function
。如果我们更改代码以使用显式名称和保留类型信息,开发人员和工具都可以使用这些信息。
typedef Compare = int Function(Object a, Object b); class SortedCollection { Compare compare; SortedCollection(this.compare); } // Initial, broken implementation. int sort(Object a, Object b) => 0; void main() { SortedCollection coll = SortedCollection(sort); assert(coll.compare is Function); assert(coll.compare is Compare); }
注意: 目前,typedefs
仅限于函数类型,可能在之后会有所改变。
因为typedef
仅仅是别名,所以它们提供了一种检查任何函数类型的方法。例如:
typedef Compare<T> = int Function(T a, T b); int sort(int a, int b) => a - b; void main() { assert(sort is Compare<int>); // True! }
元数据
使用元数据提供关于代码的附加信息。元数据注释以字符@
开头,后跟对编译时常量(如deprecated
)的引用或对常量构造函数的调用。
所有 dart 代码都可以使用两个注释:@deprecated
(弃用注释)和@override
。这里有一个使用@deprecated
注释的例子:
class Television { /// _Deprecated: Use [turnOn] instead._ @deprecated void activate() { turnOn(); } /// Turns the TV's power on. void turnOn() {...} }
可以定义自己的元数据注释(也就是类似 JavaScript 中的装饰器的效果)。这里有一个定义带有两个参数的@todo注释的示例:
library todo; class Todo { final String who; final String what; const Todo(this.who, this.what); }
这里有一个使用@todo
注释的例子:
import 'todo.dart'; @Todo('seth', 'make this do something') void doSomething() { print('do something'); }
元数据可以出现在库、类、类型定义、类型参数、构造函数、工厂、函数、字段、参数或变量声明之前,也可以出现在导入或导出指令之前。可以使用反射在运行时检索元数据。
核心库的使用
可以参考官方文档中的介绍:A tour of the core libraries
以上就是Dart 异步编程生成器及自定义类型用法详解的详细内容,更多关于Dart 异步编程生成器的资料请关注脚本之家其它相关文章!