Flutter中的Future和Stream

在 Flutter 中,FutureStream 都是用于处理异步操作的类,它们都基于 Dart 的异步编程模型,但是它们的使用场景和工作方式有所不同。以下是它们的区别以及各自适用的场景。

目录

    • 一、Future
      • 1、基本使用
      • 2、异常处理
        • [1. catchError](#1. catchError)
        • [2. onError](#2. onError)
        • [3、`catchError` 和 `onError` 的区别](#3、catchErroronError 的区别)
        • [4. 捕获多个错误](#4. 捕获多个错误)
        • [5. 错误的传播](#5. 错误的传播)
      • 总结
    • 二、Stream
      • 1、基本使用
      • 2、异常处理
        • [1. `onError` 处理器](#1. onError 处理器)
        • [2. `try-catch` 语句](#2. try-catch 语句)
        • [3. 异常后流的状态](#3. 异常后流的状态)
        • [4. 流的中断](#4. 流的中断)
        • [5. 恢复流](#5. 恢复流)
        • [6. 流异常的示例](#6. 流异常的示例)
      • 总结
    • [三、 结合使用 `Future` 和 `Stream`](#三、 结合使用 FutureStream)
    • 四、总结
      • 1、区别
      • [2、选择 `Future` 还是 `Stream`](#2、选择 Future 还是 Stream)
    • 四、疑问
      • [1、Stream 流是按照顺序执行的吗?](#1、Stream 流是按照顺序执行的吗?)
        • [1. 顺序性](#1. 顺序性)
        • [2. 顺序的保证](#2. 顺序的保证)
      • [2、Stream async* 和 yield 的解释这三个必须要配套使用吗?,不适用 yield 行吗?](#2、Stream async* 和 yield 的解释这三个必须要配套使用吗?,不适用 yield 行吗?)
        • [1. `Stream` 与 `async*`](#1. Streamasync*)
        • [2. `async*` 和 `yield` 必须配合使用吗?](#2. async*yield 必须配合使用吗?)
        • [3. 不使用 `yield` 行不行?](#3. 不使用 yield 行不行?)
        • [4. 如何使用 `async*` 生成异步数据流?](#4. 如何使用 async* 生成异步数据流?)
        • 总结

一、Future

1、基本使用

Future 是一个表示一个可能还未完成的异步操作的对象。它表示一个将来某个时间点会返回一个结果或错误的计算。

  • 特点
    • Future 代表的是一个 单次 异步操作。
    • 只能返回一次结果或错误,不会再有后续的值。
    • 在调用时,它会返回一个 Future 对象,可以通过 thenawait 等方法获取结果。
    • 如果操作失败,可以通过 catchErroronError 进行错误处理。
  • 使用场景
    • 当你需要等待一个单一的异步结果时,使用 Future
    • 比如从网络获取数据,执行一个数据库查询,或读取一个文件等一次性的操作。
  • 示例代码
dart 复制代码
// 使用 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 的回调函数。这个回调函数可以接受错误和栈跟踪信息。

示例:

dart 复制代码
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 中指定。
dart 复制代码
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

onErrorFuture 的另一种错误处理方式。它与 catchError 类似,但它是 Future 构造函数的一部分,通常用于直接在 Future 构造时附加错误处理。

示例:

dart 复制代码
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、catchErroronError 的区别
  • catchError 是用于捕获在 Future 执行时抛出的异常,它通常用于链式调用中捕获错误。
  • onErrorFuture 的一种附加错误处理机制,它将错误处理直接嵌入到 Future 构造中。

尽管 catchErroronError 都可以捕获错误并返回一个默认值或执行某些操作,但 catchError 更灵活,通常在复杂的异步链式操作中使用。

4. 捕获多个错误

如果你需要捕获多个错误,可以将 catchErroronError 绑定到多个 Future 链条上。这样可以对不同类型的错误进行不同的处理。

示例:

dart 复制代码
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 中,catchErroronError 都可以用于处理异步操作中的错误。它们的主要区别在于用法和灵活性,选择哪一个取决于你的代码结构和需求。

二、Stream

1、基本使用

Stream 是一个表示一系列异步事件的对象,它允许你在未来的时间点接收多个值。

  • 特点
    • Stream 代表的是 多个异步事件
    • 它会按顺序提供一系列的结果(可以是零个或多个),通常用于处理实时数据流。
    • 可以是单向的,也可以是广播流(多个监听者可以订阅)。
    • 你可以通过 listen 方法来监听事件流。
    • Stream 还支持 await for 语法,可以等待并处理每个事件。
  • 使用场景
    • 当你需要处理一个 数据流多个值 时,使用 Stream
    • 比如处理实时数据(如 WebSocket 数据流、用户输入事件流、文件变化等)。
  • 示例代码
dart 复制代码
// 使用 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 处理器会被触发。

dart 复制代码
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');
    },
  );
}

输出:

dart 复制代码
Received: 1
Received: 2
Caught error: Exception: Something went wrong
Stream is done

在这个例子中,当 Stream 中的 Exception 被抛出时,onError 回调会捕获并打印出错误信息。yield 3 后的代码不会执行,因为流在抛出异常后被中断。

2. try-catch 语句

在异步生成器(如 async*)中,你可以使用 try-catch 来捕获异常,这可以防止异常导致流中断。

dart 复制代码
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');
  }
}

输出:

dart 复制代码
Received: 1
Received: 2
Caught error: Exception: Something went wrong

在这个例子中,即使抛出异常,Stream 仍然能够继续执行,只是异常会被捕获并处理。

3. 异常后流的状态

Stream 抛出异常后,流会进入错误状态,并且不再发出任何数据,除非你有合适的机制来恢复流。

  • 如果流中的 onError 回调没有捕获异常,流会直接终止。
  • 如果你在 Stream 中使用 try-catch 捕获了异常,流可以继续正常工作,继续发送后续的数据。
4. 流的中断

流的中断意味着流不再继续发出事件。中断的原因通常有以下几种:

  • 异常抛出:如果流中的某个操作抛出了异常,流会被中断,后续的事件不会再触发。
  • 用户主动取消订阅 :如果你使用 StreamSubscription 来订阅流,并主动调用 cancel(),流也会中断。
  • 流结束:如果流完成(即没有更多的事件要发出),流会进入完成状态。
5. 恢复流

如果你希望在流发生异常后恢复流的工作,可以通过重新订阅流或使用一些复合的错误处理机制。

例如,在监听流时使用 onError 捕获错误并在错误发生时重新启动流:

dart 复制代码
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 生成器,它在生成某个事件时发生异常:

dart 复制代码
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');
  }
}

输出:

cpp 复制代码
Received: 1
Received: 2
Caught error: Exception: Unexpected error

在这个例子中,异常会导致流中断,后续的事件不会被处理,且异常被捕获。

总结

  • 流中断:当流遇到异常时,流会进入错误状态并停止发出事件。
  • 异常处理 :你可以通过 onError 回调或 try-catch 语句捕获和处理异常。
  • 恢复流:在流发生异常时,可以选择恢复流的工作,例如通过重新订阅流。

三、 结合使用 FutureStream

在某些情况下,FutureStream 可以结合使用。例如,如果你有一个 Future 返回一个数据集,而这个数据集可以被逐步处理,那么你可以将 Future 的结果转换成一个 Stream 来进行逐项处理。

dart 复制代码
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
返回值 只返回一个值或一个错误 持续返回多个值或错误
生命周期 一次性操作,完成后不再有新的值 持续发出多个值(或者是事件)
异步操作 适用于单次异步操作 适用于多次异步事件或数据流
操作方式 thencatchErrorawait listenawait foraddaddError

2、选择 Future 还是 Stream

  • 使用 Future
    • 你在处理一个单次的异步操作时。
    • 比如从网络获取数据、计算结果、执行数据库操作等。
    • 示例:登录请求、获取单个 API 响应、读取文件内容。
  • 使用 Stream
    • 当你需要处理 多个异步事件,例如实时数据流或变化时。
    • 比如 WebSocket 消息、实时位置更新、文件读取(大文件分片)等。
    • 示例:实时聊天消息、连续的数据更新、传感器数据流等。

四、疑问

1、Stream 流是按照顺序执行的吗?

是的,Stream 在 Dart 中是 按顺序 执行的。具体来说,Stream 中的事件(数据或错误)会按照它们被 产生发出 的顺序进行传递和处理。

1. 顺序性

在 Dart 中,Stream 会按照 事件发出的顺序 将这些事件传递给订阅者。也就是说,先发出的事件会先被监听器处理。

例如,如果你有一个 Stream 生成数据流,并且在每个数据项之间有延时,那么监听器将会按照数据产生的顺序处理每个事件。

示例代码:顺序执行的 Stream

dart 复制代码
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');  // 按顺序输出
  }
}

输出:

dart 复制代码
Received: 0
Received: 1
Received: 2

在这个例子中,数据流是按顺序传递给监听器的,即 012 按照生成的顺序被逐个输出。

2. 顺序的保证

Stream 本身的设计保证了事件的顺序。无论你是通过 await for 或者 listen 来处理事件,事件会按照发出顺序依次传递给你。

通过 listen 监听的顺序

dart 复制代码
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');  // 按顺序输出
  });
}

这段代码会输出:

dart 复制代码
Received: 0
Received: 1
Received: 2

如上所示,数据按照顺序传递。

2、Stream async* 和 yield 的解释这三个必须要配套使用吗?,不适用 yield 行吗?

在 Dart 中,Streamasync*yield 是紧密关联的,但它们不一定是必须同时使用的。它们之间的关系可以分开解释,看看如何组合使用,或者是否可以在没有 yield 的情况下使用 async*

1. Streamasync*

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,生成一个空的流

dart 复制代码
Stream<int> generateEmptyStream() async* {
  // 什么都不发出
}

void main() async {
  await for (var value in generateEmptyStream()) {
    print(value);  // 这里不会有任何输出
  }
}

在这个例子中,async* 只是声明了一个异步生成器,但是没有 yield,因此返回的 Stream 是空的,不会有任何数据输出。

4. 如何使用 async* 生成异步数据流?

async* 用于返回 Stream,可以通过 yield 来逐个发出数据。你也可以结合 await 来进行异步操作后再发出数据。这是一个典型的用法:

dart 复制代码
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);
  }
}

输出:

dart 复制代码
1
2
3
4
5

在这个例子中,async* 通过 yield 发出了多个数字,每次发出时都延迟 1 秒。

总结
  • async*yield 通常一起使用来生成和发出异步数据流。
  • 不一定非要有 yield (但不使用 yield 意义不大 ),但如果你希望生成一个有数据的流,就需要使用 yield 或其他发出数据的方式(例如 yield*)。
  • 如果你在 async* 中没有 yield,返回的 Stream 将不会发出任何数据,通常这种情况用于创建空流或只执行异步操作的函数。

因此,虽然 async*yield 是紧密相关的,但它们不必总是同时使用。如果不使用 yield,可以生成一个空流或执行异步操作,但不会有数据发出。

相关推荐
Wuxiaoming1358 小时前
关于flutter与django建立mqtt的研究
flutter
Cao_Shixin攻城狮12 小时前
[Flutter]Json序列化json_serializable使用属性全介绍
flutter·json·serializable
五味香1 天前
Java学习,反射
android·java·开发语言·python·学习·flutter·kotlin
收银系统源码那点事1 天前
零售餐饮收银台源码
flutter·uni-app·零售·收银系统源码·收银系统·连锁店收银系统
唯鹿1 天前
Flutter如何适配RTL
android·flutter
Mis_wenwen1 天前
flutter 解决webview加载重定向h5页面 返回重复加载问题
flutter·h5·webview·重定向
兰琛1 天前
Flutter 1.1:下载Flutter环境
android·flutter
sunly_1 天前
Flutter:购物车总结(单选,全选,定义数量,是否选中为obs响应式)
flutter
sunly_2 天前
Flutter:常见的页面布局:上边内容可滚动,底部固定一个按钮
android·javascript·flutter