Dart语言async关键字详解
在 Dart 语言中,async 关键字用于指示一个函数是异步函数,该函数可以异步执行代码并返回一个 Future 对象,而不是直接返回结果。
在一个 async 函数中,可以使用 await 关键字来等待一个 Future 对象的结果,当遇到 await 关键字时,执行流会暂停,直到该 Future 对象返回结果或抛出异常。
例如,以下是一个简单的使用 async/await
的示例:
Future<void> printData() async {
String data = await fetchData();
print(data);
}
Future<String> fetchData() {
// 模拟异步操作
return Future.delayed(Duration(seconds: 2), () => "Hello, World!");
}
void main() {
printData();
print("Fetching data...");
}
在上面的代码中,printData 函数使用 async 关键字声明为异步函数,它调用 fetchData 函数并等待它的返回结果。fetchData 函数模拟一个异步操作,返回一个 Future 对象,在 2 秒后返回一个字符串 "Hello, World!"
。
在 main 函数中,首先调用 printData 函数,它会异步执行并等待 fetchData 函数返回结果,然后打印返回的字符串。同时,main 函数继续执行并打印 "Fetching data..."
。
在运行上面的代码后,输出结果应该是:
Fetching data...
Hello, World!
可以看到,在 printData 函数中,使用 await 关键字等待 fetchData 函数返回结果时,执行流会暂停,直到 fetchData 函数返回结果,然后才会继续执行下一条语句打印返回的字符串。
async 关键字和 await 关键字是 Dart 语言中用于处理异步操作的两个关键工具,它们可以帮助开发者编写更具可读性和可维护性的异步代码。
除了常规的异步编程模式(如 Future 和 Stream),Dart 的 async/await 还支持一些高级用法,包括以下几种:
1.使用 Completer 和 Future 构建自定义的异步操作。
Completer 是一个非常灵活的工具,它允许我们手动创建 Future 对象,并在需要的时候手动完成这个 Future。通过使用 Completer,我们可以轻松地构建自定义的异步操作,这些操作可以与现有的异步 API 协同工作,或者用于一些特定的场景。下面是一个使用 Completer 构建异步操作的例子:
Future<int> delayedSum(int a, int b) {
Completer<int> completer = Completer<int>();
Future.delayed(Duration(seconds: 1), () {
completer.complete(a + b);
});
return completer.future;
}
void main() async {
int result = await delayedSum(1, 2);
print(result); // 3
}
2.使用 StreamController 和 Stream 构建异步数据流。
除了使用 Future 之外,Dart 还提供了 Stream 类来表示异步数据流。我们可以使用 StreamController 创建一个新的 Stream,并通过 add 方法向这个 Stream 中添加数据。下面是一个使用 StreamController 构建异步数据流的例子:
Stream<int> countDown(int seconds) async* {
for (int i = seconds; i >= 0; i--) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
void main() async {
Stream<int> stream = countDown(5);
await for (int i in stream) {
print(i);
}
print('Countdown complete!');
}
在这个例子中,我们使用 async* 关键字来定义一个异步数据流。然后,我们在 for 循环中使用 await for 来等待 Stream 中的数据,并在每次接收到数据时打印它。
3.使用 StreamTransformer 和 StreamSubscription 实现高级的流处理操作。
除了简单的数据流操作之外,Dart 还提供了一些流处理操作,如过滤、映射、组合、合并等等。这些操作可以通过 StreamTransformer 来实现,可以将一个流转换为另一个流,并在转换过程中执行一些自定义的操作。下面是一个使用 StreamTransformer 进行流处理的例子:
Stream<int> countDown(int seconds) async* {
for (int i = seconds; i >= 0; i--) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
void main() async {
Stream<int> stream = countDown(5)
.where((x) => x % 2 == 0)
.map((x) => x * 2)
.take(3);
await for (int i in stream) {
print(i);
}
print('Countdown complete!');
}
4.使用 StreamGroup 和 StreamZip 对多个流进行操作。
当我们需要同时处理多个异步数据流时,可以使用 StreamGroup 或 StreamZip。StreamGroup 可以将多个流组合成一个新的流,这个新的流中的数据按照原来的流的顺序进行排列。StreamZip 可以将多个流中的数据按照一定的规则进行组合。下面是一个使用 StreamGroup 对多个流进行操作的例子:
Stream<int> countDown(int seconds) async* {
for (int i = seconds; i >= 0; i--) {
await Future.delayed(Duration(seconds: 1));
yield i;
}
}
void main() async {
StreamGroup<int> group = StreamGroup<int>();
Stream<int> stream1 = countDown(5);
Stream<int> stream2 = countDown(3).map((x) => x * 2);
Stream<int> stream3 = countDown(4).map((x) => x * x);
group.add(stream1);
group.add(stream2);
group.add(stream3);
Stream<int> stream = group.stream;
await for (int i in stream) {
print(i);
}
print('Countdown complete!');
}
在这个例子中,我们使用 StreamGroup 将三个异步数据流组合成一个新的流,并按照原来的流的顺序排列数据。然后,我们使用 await for 循环来遍历这个新的流,并在每次接收到数据时打印它。
5.使用 Future.wait() 同时等待多个异步操作完成。
如果我们需要同时等待多个异步操作完成,可以使用 Future.wait() 方法。这个方法接收一个包含多个 Future 对象的列表,并返回一个新的 Future 对象,这个新的 Future 对象在列表中的所有 Future 对象都完成时才会完成。下面是一个使用 Future.wait() 同时等待多个异步操作完成的例子:
Future<int> delayedSum(int a, int b, int delay) async {
await Future.delayed(Duration(seconds: delay));
return a + b;
}
void main() async {
Future<int> future1 = delayedSum(1, 2, 2);
Future<int> future2 = delayedSum(3, 4, 1);
Future<int> future3 = delayedSum(5, 6, 3);
List<int> results = await Future.wait([future1, future2, future3]);
print(results); // [3, 7, 11]
}
在这个例子中,我们使用 Future.wait() 方法同时等待三个异步操作完成,并在它们都完成时打印它们的结果。
在使用 async/await
进行异步编程时,需要注意以下几点:
- 异步函数返回的是一个 Future 对象,可以使用 then 方法或者 await 关键字来获取异步操作的结果,否则异步操作将不会被执行。
- 异步函数内部的代码会被封装在一个微任务中,因此需要注意异步函数中可能会抛出的异常,避免在异步函数内部使用 try-catch 来捕获异常,这样可以确保异常可以被捕获并处理。
- 在使用 await 关键字等待异步操作完成时,需要注意如果等待的异步操作抛出了异常,则 await 关键字后面的代码将不会被执行,可以使用 try-catch 来捕获这些异常。
- 如果在同一个异步函数中多次使用 await 关键字等待异步操作完成,这些操作将会按照顺序执行,如果其中一个操作抛出了异常,则后面的操作将不会被执行。
- 在使用 Future.wait 方法等待多个异步操作完成时,需要注意这些操作是并发执行的,因此可能会影响程序的性能和资源占用,需要根据具体情况选择合适的并发度。
- 在使用 Stream 进行异步编程时,需要注意 Stream 中的数据可能会在任何时间到达,因此需要使用 StreamSubscription 对象来监听并处理 Stream 中的数据和错误。
异步编程是一个非常强大和灵活的编程技术,可以帮助我们编写更高效和可维护的代码,但是在使用 async/await 进行异步编程时,需要注意以上几点以避免一些常见的错误和问题。