在 Flutter 中,Future
和 Stream
都是用于处理异步操作的类,它们都基于 Dart 的异步编程模型,但是它们的使用场景和工作方式有所不同。以下是它们的区别以及各自适用的场景。
目录
- 一、Future
- 1、基本使用
- 2、异常处理
- 1. catchError
- 2. onError
- 3、`catchError` 和 `onError` 的区别
- 4. 捕获多个错误
- 5. 错误的传播
- 总结
- 二、Stream
- 1、基本使用
- 2、异常处理
- 1. `onError` 处理器
- 2. `try-catch` 语句
- 3. 异常后流的状态
- 4. 流的中断
- 5. 恢复流
- 6. 流异常的示例
- 总结
- 三、 结合使用 `Future` 和 `Stream`
- 四、总结
- 1、区别
- 2、选择 `Future` 还是 `Stream`
- 四、疑问
- 1、Stream 流是按照顺序执行的吗?
- 1. 顺序性
- 2. 顺序的保证
- 2、Stream async* 和 yield 的解释这三个必须要配套使用吗?,不适用 yield 行吗?
- 1. `Stream` 与 `async*`
- 2. `async*` 和 `yield` 必须配合使用吗?
- 3. 不使用 `yield` 行不行?
- 4. 如何使用 `async*` 生成异步数据流?
- 总结
一、Future
1、基本使用
Future
是一个表示一个可能还未完成的异步操作的对象。它表示一个将来某个时间点会返回一个结果或错误的计算。
- 特点:
Future
代表的是一个 单次 异步操作。- 只能返回一次结果或错误,不会再有后续的值。
- 在调用时,它会返回一个
Future
对象,可以通过then
或await
等方法获取结果。 - 如果操作失败,可以通过
catchError
或onError
进行错误处理。
- 使用场景:
- 当你需要等待一个单一的异步结果时,使用
Future
。 - 比如从网络获取数据,执行一个数据库查询,或读取一个文件等一次性的操作。
- 当你需要等待一个单一的异步结果时,使用
- 示例代码:
// 使用 Future 的示例
Future<String> fetchData() async {// 模拟异步操作await Future.delayed(Duration(seconds: 2));return "Data fetched successfully!";
}void main() async {try {String data = await fetchData();print(data); // 输出 "Data fetched successfully!"} catch (e) {print("Error: $e");}
}
在上面的示例中,fetchData
返回一个 Future<String>
,它表示一个将来完成的异步操作。使用 await
来等待这个操作完成并获得结果。
2、异常处理
1. catchError
catchError
是用于捕获和处理 Future
发生错误的一种方式。当 Future
执行失败时,它会触发传给 catchError
的回调函数。这个回调函数可以接受错误和栈跟踪信息。
示例:
Future<int> divide(int a, int b) {return Future.delayed(Duration(seconds: 1), () {if (b == 0) {throw Exception('Cannot divide by zero!');}return a ~/ b;});
}void main() {divide(10, 0).catchError((e) {print('Error: $e');});// Output: Error: Exception: Cannot divide by zero!
}
在这个例子中,当 b
为 0 时,Future
会抛出一个 Exception
,并且 catchError
捕获并打印这个错误。
catchError
的用法细节:
- 返回值的传递:
catchError
会继续执行Future
链中的后续操作,因此如果你想在错误发生时返回一个默认值,可以在catchError
中指定。
Future<int> divide(int a, int b) {return Future.delayed(Duration(seconds: 1), () {if (b == 0) {throw Exception('Cannot divide by zero!');}return a ~/ b;}).catchError((e) {print('Handled error: $e');return -1; // 返回默认值});
}void main() async {var result = await divide(10, 0);print('Result: $result'); // 输出: Handled error: Exception: Cannot divide by zero!// Result: -1
}
2. onError
onError
是 Future
的另一种错误处理方式。它与 catchError
类似,但它是 Future
构造函数的一部分,通常用于直接在 Future
构造时附加错误处理。
示例:
Future<int> divide(int a, int b) {return Future.delayed(Duration(seconds: 1), () {if (b == 0) {throw Exception('Cannot divide by zero!');}return a ~/ b;}).onError((error, stackTrace) {print('Caught an error: $error');return -1; // 返回默认值});
}void main() async {var result = await divide(10, 0);print('Result: $result'); // 输出: Caught an error: Exception: Cannot divide by zero!// Result: -1
}
3、catchError
和 onError
的区别
catchError
是用于捕获在Future
执行时抛出的异常,它通常用于链式调用中捕获错误。onError
是Future
的一种附加错误处理机制,它将错误处理直接嵌入到Future
构造中。
尽管 catchError
和 onError
都可以捕获错误并返回一个默认值或执行某些操作,但 catchError
更灵活,通常在复杂的异步链式操作中使用。
4. 捕获多个错误
如果你需要捕获多个错误,可以将 catchError
或 onError
绑定到多个 Future
链条上。这样可以对不同类型的错误进行不同的处理。
示例:
Future<void> asyncFunction() {return Future.delayed(Duration(seconds: 1), () {throw Exception('Something went wrong!');});
}void main() {asyncFunction().catchError((e) {print('Caught error: $e');}).catchError((e) {print('Another handler for errors: $e');});// Output: Caught error: Exception: Something went wrong!
}
5. 错误的传播
如果在 Future
中没有处理错误,错误将会被传播,直到被外部捕获或程序崩溃。因此,适当的错误处理不仅可以帮助捕获问题,还可以避免未捕获的异常导致程序崩溃。
总结
在 Dart 中,catchError
和 onError
都可以用于处理异步操作中的错误。它们的主要区别在于用法和灵活性,选择哪一个取决于你的代码结构和需求。
二、Stream
1、基本使用
Stream
是一个表示一系列异步事件的对象,它允许你在未来的时间点接收多个值。
- 特点:
Stream
代表的是 多个异步事件。- 它会按顺序提供一系列的结果(可以是零个或多个),通常用于处理实时数据流。
- 可以是单向的,也可以是广播流(多个监听者可以订阅)。
- 你可以通过
listen
方法来监听事件流。 Stream
还支持await for
语法,可以等待并处理每个事件。
- 使用场景:
- 当你需要处理一个 数据流 或 多个值 时,使用
Stream
。 - 比如处理实时数据(如 WebSocket 数据流、用户输入事件流、文件变化等)。
- 当你需要处理一个 数据流 或 多个值 时,使用
- 示例代码:
// 使用 Stream 的示例
Stream<int> generateNumbers() async* {for (int i = 0; i < 5; i++) {await Future.delayed(Duration(seconds: 1));yield i; // 每秒产生一个数字}
}void main() async {await for (var number in generateNumbers()) {print(number); // 输出:0, 1, 2, 3, 4}
}
在这个示例中,generateNumbers
返回一个 Stream<int>
,它每秒返回一个整数。通过 await for
循环,我们可以逐个接收流中的数据。
2、异常处理
当流中发生异常时,有几种方式来处理这些异常,使得流能够继续工作或适当地终止。
1. onError
处理器
如果你使用 Stream.listen
方法来监听流,可以通过传入 onError
回调来处理流中的异常。当流抛出异常时,onError
处理器会被触发。
Stream<int> generateNumbersWithError() async* {yield 1;yield 2;throw Exception('Something went wrong');yield 3; // 这一行永远不会执行
}void main() {generateNumbersWithError().listen((data) {print('Received: $data');},onError: (error) {print('Caught error: $error');},onDone: () {print('Stream is done');},);
}
输出:
Received: 1
Received: 2
Caught error: Exception: Something went wrong
Stream is done
在这个例子中,当 Stream
中的 Exception
被抛出时,onError
回调会捕获并打印出错误信息。yield 3
后的代码不会执行,因为流在抛出异常后被中断。
2. try-catch
语句
在异步生成器(如 async*
)中,你可以使用 try-catch
来捕获异常,这可以防止异常导致流中断。
Stream<int> generateNumbersWithErrorHandled() async* {try {yield 1;yield 2;throw Exception('Something went wrong');yield 3; // 这一行不会执行} catch (e) {print('Caught error: $e');}
}void main() async {await for (var data in generateNumbersWithErrorHandled()) {print('Received: $data');}
}
输出:
Received: 1
Received: 2
Caught error: Exception: Something went wrong
在这个例子中,即使抛出异常,Stream
仍然能够继续执行,只是异常会被捕获并处理。
3. 异常后流的状态
当 Stream
抛出异常后,流会进入错误状态,并且不再发出任何数据,除非你有合适的机制来恢复流。
- 如果流中的
onError
回调没有捕获异常,流会直接终止。 - 如果你在
Stream
中使用try-catch
捕获了异常,流可以继续正常工作,继续发送后续的数据。
4. 流的中断
流的中断意味着流不再继续发出事件。中断的原因通常有以下几种:
- 异常抛出:如果流中的某个操作抛出了异常,流会被中断,后续的事件不会再触发。
- 用户主动取消订阅:如果你使用
StreamSubscription
来订阅流,并主动调用cancel()
,流也会中断。 - 流结束:如果流完成(即没有更多的事件要发出),流会进入完成状态。
5. 恢复流
如果你希望在流发生异常后恢复流的工作,可以通过重新订阅流或使用一些复合的错误处理机制。
例如,在监听流时使用 onError
捕获错误并在错误发生时重新启动流:
Stream<int> generateNumbersWithError() async* {yield 1;yield 2;throw Exception('Something went wrong');yield 3;
}void main() {Stream<int> stream = generateNumbersWithError();stream.listen((data) {print('Received: $data');},onError: (error) {print('Caught error: $error');// 重新启动流stream.listen((data) => print('Retry received: $data'),onError: (e) => print('Retry error: $e'),onDone: () => print('Retry stream is done'),);},onDone: () => print('Stream is done'),);
}
在这种情况下,流会在错误发生时重新启动。这允许你捕获错误并尝试恢复流的执行。
6. 流异常的示例
假设有一个 Stream
生成器,它在生成某个事件时发生异常:
Stream<int> generateNumbersWithError() async* {yield 1;yield 2;throw Exception('Unexpected error');yield 3; // 这行永远不会执行
}void main() async {try {await for (var number in generateNumbersWithError()) {print('Received: $number');}} catch (e) {print('Caught error: $e');}
}
输出:
Received: 1
Received: 2
Caught error: Exception: Unexpected error
在这个例子中,异常会导致流中断,后续的事件不会被处理,且异常被捕获。
总结
- 流中断:当流遇到异常时,流会进入错误状态并停止发出事件。
- 异常处理:你可以通过
onError
回调或try-catch
语句捕获和处理异常。 - 恢复流:在流发生异常时,可以选择恢复流的工作,例如通过重新订阅流。
三、 结合使用 Future
和 Stream
在某些情况下,Future
和 Stream
可以结合使用。例如,如果你有一个 Future
返回一个数据集,而这个数据集可以被逐步处理,那么你可以将 Future
的结果转换成一个 Stream
来进行逐项处理。
Future<List<int>> fetchData() async {return [1, 2, 3, 4, 5];
}Stream<int> fetchDataAsStream() async* {List<int> data = await fetchData();for (var item in data) {yield item;}
}void main() async {await for (var number in fetchDataAsStream()) {print(number); // 输出 1, 2, 3, 4, 5}
}
在这个例子中,fetchData
是一个 Future
,而 fetchDataAsStream
将其转换成了一个 Stream
,使得我们能够以流的形式逐项处理数据。
总结
Future
适合用于处理 单次 的异步操作,返回一个值或错误。Stream
适合用于处理 多次 的异步事件或数据流,允许你持续接收多个值。
了解它们的区别和使用场景可以帮助你更好地选择异步操作的方式,从而提高代码的可读性和性能。
四、总结
1、区别
特性 | Future | Stream |
---|---|---|
返回值 | 只返回一个值或一个错误 | 持续返回多个值或错误 |
生命周期 | 一次性操作,完成后不再有新的值 | 持续发出多个值(或者是事件) |
异步操作 | 适用于单次异步操作 | 适用于多次异步事件或数据流 |
操作方式 | then 、catchError 、await | listen 、await for 、add 、addError |
2、选择 Future
还是 Stream
- 使用
Future
:- 你在处理一个单次的异步操作时。
- 比如从网络获取数据、计算结果、执行数据库操作等。
- 示例:登录请求、获取单个 API 响应、读取文件内容。
- 使用
Stream
:- 当你需要处理 多个异步事件,例如实时数据流或变化时。
- 比如 WebSocket 消息、实时位置更新、文件读取(大文件分片)等。
- 示例:实时聊天消息、连续的数据更新、传感器数据流等。
四、疑问
1、Stream 流是按照顺序执行的吗?
是的,Stream
在 Dart 中是 按顺序 执行的。具体来说,Stream
中的事件(数据或错误)会按照它们被 产生 或 发出 的顺序进行传递和处理。
1. 顺序性
在 Dart 中,Stream
会按照 事件发出的顺序 将这些事件传递给订阅者。也就是说,先发出的事件会先被监听器处理。
例如,如果你有一个 Stream
生成数据流,并且在每个数据项之间有延时,那么监听器将会按照数据产生的顺序处理每个事件。
示例代码:顺序执行的 Stream
Stream<int> generateNumbers() async* {for (int i = 0; i < 3; i++) {await Future.delayed(Duration(seconds: 1)); // 模拟延时yield i; // 按顺序发出数据}
}void main() async {await for (var number in generateNumbers()) {print('Received: $number'); // 按顺序输出}
}
输出:
Received: 0
Received: 1
Received: 2
在这个例子中,数据流是按顺序传递给监听器的,即 0
、1
、2
按照生成的顺序被逐个输出。
2. 顺序的保证
Stream
本身的设计保证了事件的顺序。无论你是通过 await for
或者 listen
来处理事件,事件会按照发出顺序依次传递给你。
通过 listen
监听的顺序
Stream<int> generateNumbers() async* {for (int i = 0; i < 3; i++) {await Future.delayed(Duration(seconds: 1));yield i;}
}void main() {generateNumbers().listen((data) {print('Received: $data'); // 按顺序输出});
}
这段代码会输出:
Received: 0
Received: 1
Received: 2
如上所示,数据按照顺序传递。
2、Stream async* 和 yield 的解释这三个必须要配套使用吗?,不适用 yield 行吗?
在 Dart 中,Stream
、async*
和 yield
是紧密关联的,但它们不一定是必须同时使用的。它们之间的关系可以分开解释,看看如何组合使用,或者是否可以在没有 yield
的情况下使用 async*
。
1. Stream
与 async*
Stream
是 Dart 中用于处理异步数据流的核心概念。Stream
对象用于处理一系列异步事件,而 async*
是定义异步生成器(异步迭代器)的一种语法。async*
允许你生成一个 Stream
,并通过 yield
来发出数据。
async*
标识一个异步生成器函数,它返回一个Stream
。yield
用于在异步生成器中逐个发出数据。
2. async*
和 yield
必须配合使用吗?
async*
和yield
一般是配套使用的,但你也可以只使用async*
,并没有强制要求一定要用yield
。- 如果你不需要发出数据(即你不想使用
yield
),你也可以用async*
作为一个简单的异步函数来返回一个空的Stream
,或者使用await
来发出异步的结果。
3. 不使用 yield
行不行?
是的,可以在没有 yield
的情况下使用 async*
,但通常这样做的结果是流不会发出任何数据。在这种情况下,Stream
会是一个空的流,或者说它在没有任何数据的情况下完成。
示例 1:不使用 yield
,生成一个空的流
Stream<int> generateEmptyStream() async* {// 什么都不发出
}void main() async {await for (var value in generateEmptyStream()) {print(value); // 这里不会有任何输出}
}
在这个例子中,async*
只是声明了一个异步生成器,但是没有 yield
,因此返回的 Stream
是空的,不会有任何数据输出。
4. 如何使用 async*
生成异步数据流?
async*
用于返回 Stream
,可以通过 yield
来逐个发出数据。你也可以结合 await
来进行异步操作后再发出数据。这是一个典型的用法:
Stream<int> generateNumbers() async* {for (int i = 1; i <= 5; i++) {await Future.delayed(Duration(seconds: 1)); // 模拟异步操作yield i; // 发出数据}
}void main() async {await for (var number in generateNumbers()) {print(number);}
}
输出:
1
2
3
4
5
在这个例子中,async*
通过 yield
发出了多个数字,每次发出时都延迟 1 秒。
总结
async*
和yield
通常一起使用来生成和发出异步数据流。- 不一定非要有
yield
(但不使用 yield 意义不大),但如果你希望生成一个有数据的流,就需要使用yield
或其他发出数据的方式(例如yield*
)。 - 如果你在
async*
中没有yield
,返回的Stream
将不会发出任何数据,通常这种情况用于创建空流或只执行异步操作的函数。
因此,虽然 async*
和 yield
是紧密相关的,但它们不必总是同时使用。如果不使用 yield
,可以生成一个空流或执行异步操作,但不会有数据发出。