Flutter 异步与同步(2):Flutter中同步异步的使用

一、StreamBuilder/FutureBuilder

在 Flutter 中,StreamBuilderFutureBuilder 都是用于处理异步数据源的常用组件。它们允许你在等待异步操作完成时更新 UI。尽管它们的功能类似,但它们适用于不同的异步数据源类型。


1.1 FutureBuilder

FutureBuilder 用于处理 Future 类型的异步数据源。Future 表示一个将来可能会完成的单一异步操作,例如一次性网络请求。

适用场景

  • 当你有一个一次性的异步操作,例如从网络加载数据,执行数据库查询等。

构造函数参数

  • future: 一个 Future 对象,表示要等待的异步操作。

  • builder: 一个函数,构建UI,并根据 Future 的状态来更新UI。

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(
  debugShowCheckedModeBanner: false,
  home: MyFutureBuilder(),
  ));
 
class MyFutureBuilder extends StatelessWidget {
  const MyFutureBuilder({super.key});

  Future<String> fetchData() async {
    await Future.delayed(const Duration(seconds: 5)); // 模拟网络延迟
    return 'Hello, FutureBuilder!';
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('FutureBuilder Example')),
      body: Center(
        child: FutureBuilder<String>(
          future: fetchData(), // 要等待的异步操作
          builder: (BuildContext context, AsyncSnapshot<String> snapshot) { // 构建UI,并根据 Future 的状态来更新UI
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator(); // 圆形进度条
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            } else {
              return Text('Result: ${snapshot.data}');
            }
          },
        ),
      ),
    );
  }
}

等待 5 秒网络延迟后,可以看到文本显示为 "Hello, FutureBuilder!"。

等待异步操作时的状态图:

Flutter_async_C.png


5 秒网络延迟后,完成异步操作后的状态图:

Flutter_async_D.png


1.2 StreamBuilder

StreamBuilder 用于处理 Stream 类型的异步数据源。Stream 表示一系列的异步事件或数据,例如连续的传感器数据、WebSocket 数据等。

适用场景

  • 当你有多个异步数据或事件的流,例如实时更新的数据、传感器数据等。

构造函数参数

  • stream: 一个 Stream 对象,表示要监听的异步数据流。

  • builder: 一个函数,构建UI,并根据 Stream 的状态和数据来更新UI。

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(
  debugShowCheckedModeBanner: false,
  home: MyStreamBuilder(),
  ));
 
class MyStreamBuilder extends StatelessWidget {
  const MyStreamBuilder({super.key});

  Stream<int> counterStream() async* {
    for (int i = 0; i < 10; i++) {
      await Future.delayed(const Duration(seconds: 1));
      yield i;
    }
  }
 
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('StreamBuilder Example')),
      body: Center(
        child: StreamBuilder<int>(
          stream: counterStream(), // 要等待的异步操作
          builder: (BuildContext context, AsyncSnapshot<int> snapshot) { // 构建UI,并根据 Future 的状态来更新UI
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            } else if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            } else if (!snapshot.hasData) {
              return const Text('No data');
            } else {
              return Text('Counter: ${snapshot.data}');
            }
          },
        ),
      ),
    );
  }
}

可以看到数量从 1 到 9,效果图如下所示:

Flutter_async_A.gif


1.3 优缺点

FutureBuilder

优点

  • 简单易用: 适合处理一次性异步操作,只需关心数据何时完成,代码逻辑简单明了。
  • 轻量: 只处理一次异步数据,因此不需要持续监听或管理数据流,内存和资源消耗较少。
  • 减少复杂性: 不需要考虑多次数据更新的处理逻辑,适用于单次数据请求或简单任务。

缺点

数据不可更新: FutureBuilder 只会在 Future 完成时更新一次,无法处理后续的数据变化。如果需要实时更新数据,需要重新触发整个组件的构建。
无法处理长时间操作: 对于需要长时间处理或持续更新的操作不适用,必须使用 StreamBuilder 或其它机制。


StreamBuilder

优点

  • 实时更新: 可以处理多次数据更新,每当有新的数据事件时,都会自动更新 UI,适合需要实时响应数据变化的场景。
  • 强大的灵活性: 适用于多种异步数据流处理场景,如 WebSocket 连接、传感器数据、用户输入等,具备更高的适应性。
  • 多事件处理: 可以监听和响应连续的多个异步事件,提供更丰富的交互体验。

缺点

  • 复杂性较高: 由于需要处理多个事件,代码逻辑相对复杂,尤其是在处理错误、空数据或状态变化时,可能需要更多的管理代码。
  • 资源消耗大: 持续监听数据流可能会占用更多的内存和CPU资源,尤其是长时间运行的操作。如果处理不当,可能会导致性能问题或资源泄漏。
  • 潜在的状态管理问题: 如果不注意数据流的管理和清理,可能会引入内存泄漏或不必要的状态更新。

适用场景

  • FutureBuilder: 适用于一次性数据加载场景,如启动时加载配置、单次API请求等。
  • StreamBuilder: 适用于需要持续监听和更新UI的场景,如实时聊天、状态监控、数据推送等。
  • 选择 FutureBuilder 还是 StreamBuilder 取决于你的需求。如果是一次性任务且不需要后续更新,FutureBuilder 是更简单的选择。如果需要处理多次数据更新或实时响应,StreamBuilder 则提供了更大的灵活性和功能。

二、Future.wait/Future.any

2.1 Future.wait

Future.wait 等待所有提供的 Future 对象完成,并在所有 Future 都完成后返回一个包含每个 Future 结果的列表。如果任何一个 Future 抛出异常,则 Future.wait 会返回第一个抛出的异常。

适用场景

  • 当你有多个并发的异步任务,并且需要等待所有任务都完成后再继续执行后续操作。
import 'package:flutter/material.dart';
import 'dart:async';
 
void main() async {
  debugPrint('Start');
 
  Future<int> task1 = Future.delayed(const Duration(seconds: 2), () => 1);
  Future<int> task2 = Future.delayed(const Duration(seconds: 3), () => 2);
  Future<int> task3 = Future.delayed(const Duration(seconds: 1), () => 3);
 
  List<int> results = await Future.wait([task1, task2, task3]);
  debugPrint('Results: $results'); // [1, 2, 3]
 
  debugPrint('End');
}

打印输出:

flutter: Start
flutter: Results: [1, 2, 3]
flutter: End

2.2 Future.any

Future.any 等待提供的 Future 对象中第一个完成的任务,并返回该任务的结果。如果所有 Future 都抛出异常,则 Future.any 会返回最后一个抛出的异常。

适用场景

  • 当你有多个并发的异步任务,并且只需要其中任何一个任务完成后就继续执行后续操作。
import 'package:flutter/material.dart';
import 'dart:async';
 
void main() async {
  debugPrint('Start');
 
  Future<int> task1 = Future.delayed(const Duration(seconds: 2), () => 1);
  Future<int> task2 = Future.delayed(const Duration(seconds: 3), () => 2);
  Future<int> task3 = Future.delayed(const Duration(seconds: 1), () => 3);
 
  int result = await Future.any([task1, task2, task3]);
  debugPrint('First completed result: $result'); // 3
 
  debugPrint('End');
}

在这个示例中,Future.any 会在 task3 完成后立即返回结果,因为 task3 是最先完成的任务。打印输出:

flutter: Start
flutter: First completed result: 3
flutter: End

2.3 区别总结

  • 行为方式:

    • Future.wait 等待所有 Future 对象完成,并返回所有结果。
    • Future.any 等待第一个完成的 Future 对象,并返回其结果。
  • 错误处理:

    • Future.wait 如果任何一个 Future 抛出异常,则返回第一个抛出的异常。
    • Future.any 如果所有 Future 都抛出异常,则返回最后一个抛出的异常。
  • 适用场景:

    • Future.wait 适用于需要所有并发任务都完成后再处理结果的场景。
    • Future.any 适用于只需要其中任何一个任务完成即可继续执行的场景。

2.4 实践中的应用场景

使用 Future.wait

假设你在开发一个应用,需要同时发起多个网络请求,只有在所有请求完成后才能继续处理响应数据。这时你可以使用 Future.wait

import 'package:flutter/material.dart';
import 'dart:async';
 
void main() async {
  Future<String> fetchUserData() async {
    await Future.delayed(const Duration(seconds: 2));
    return 'User data';
  }
 
  Future<String> fetchOrders() async {
    await Future.delayed(const Duration(seconds: 3));
    return 'Orders';
  }
 
  Future<String> fetchSettings() async {
    await Future.delayed(const Duration(seconds: 1));
    return 'Settings';
  }
 
  debugPrint('Fetching data...');
 
  List<String> results = await Future.wait([
    fetchUserData(),
    fetchOrders(),
    fetchSettings(),
  ]);
 
  debugPrint('All data fetched: $results');
}

打印输出:

flutter: Fetching data...
flutter: All data fetched: [User data, Orders, Settings]

使用 Future.any

假设你在开发一个应用,需要从多个服务器中获取数据,只要任何一个服务器返回数据即可。这时你可以使用 Future.any

import 'package:flutter/material.dart';
import 'dart:async';
 
void main() async {
  Future<String> fetchFromServer1() async {
    await Future.delayed(const Duration(seconds: 2));
    return 'Data from server 1';
  }
 
  Future<String> fetchFromServer2() async {
    await Future.delayed(const Duration(seconds: 3));
    return 'Data from server 2';
  }
 
  Future<String> fetchFromServer3() async {
    await Future.delayed(const Duration(seconds: 1));
    return 'Data from server 3';
  }
 
  debugPrint('Fetching data...');
 
  String result = await Future.any([
    fetchFromServer1(),
    fetchFromServer2(),
    fetchFromServer3(),
  ]);
 
  debugPrint('First data fetched: $result');
}

打印输出:

flutter: Fetching data...
flutter: First data fetched: Data from server 3

三、Future.doWhile/Future.forEach

3.1 Future.doWhile

Future.doWhile 用于执行一个异步循环,直到条件不满足为止。循环体中的每次迭代是异步的,并且需要返回一个 Future<bool>,表示是否继续循环。

用法

  • 适用于需要重复执行异步操作,直到某个条件不再满足的场景。
import 'package:flutter/material.dart';
import 'dart:async';
 
void main() async {
  int counter = 0;
 
  debugPrint('Start');
 
  await Future.doWhile(() async {
    counter++;
    debugPrint('Counter: $counter');
    await Future.delayed(const Duration(seconds: 1)); // 模拟异步操作
    return counter < 5; // 继续循环直到 counter >= 5
  });
 
  debugPrint('End');
}

打印输出:

flutter: Start
flutter: Counter: 1
flutter: Counter: 2
flutter: Counter: 3
flutter: Counter: 4
flutter: Counter: 5
flutter: End

3.2 Future.forEach

Future.forEach 用于对集合中的每个元素执行异步操作。它依次执行每个元素的异步操作,等待当前操作完成后再执行下一个操作。

用法

  • 适用于需要对集合中的每个元素执行异步操作的场景。
import 'package:flutter/material.dart';
import 'dart:async';
 
void main() async {
  List<int> numbers = [1, 2, 3, 4, 5];
 
  debugPrint('Start');
 
  await Future.forEach(numbers, (number) async {
    debugPrint('Processing number: $number');
    await Future.delayed(const Duration(seconds: 1)); // 模拟异步操作
  });
 
  debugPrint('End');
}

打印输出:

flutter: Start
flutter: Processing number: 1
flutter: Processing number: 2
flutter: Processing number: 3
flutter: Processing number: 4
flutter: Processing number: 5
flutter: End

3.3 区别总结

  • 控制流方式:
    • Future.doWhile:用于执行一个异步循环,直到条件返回 false 为止。适合在不知道具体迭代次数的情况下执行重复的异步操作。
    • Future.forEach:用于对集合中的每个元素执行异步操作,适合在知道具体迭代次数的情况下对集合进行异步处理。
  • 返回类型:
    • Future.doWhile:接受一个返回 Future<bool> 的函数作为参数。
    • Future.forEach:接受一个返回 Future<void> 的函数作为参数。
  • 终止条件:
    • Future.doWhile:循环的终止条件由函数返回的布尔值决定。
    • Future.forEach:循环的终止条件是集合中所有元素都处理完毕。
  • 应用场景:
    • Future.doWhile:适用于需要重复执行异步操作,直到某个条件不再满足的情况。
    • Future.forEach:适用于需要对集合的每个元素执行异步操作的情况。

四、Future异常处理

Flutter 和 Dart 提供了几种方法来捕获和处理 Future 异常,包括使用 try-catch 语句、catchError 方法以及在异步函数中处理异常。


4.1 try-catch

import 'package:flutter/material.dart';
import 'dart:async';
 
Future<void> fetchData() async {
  try {
    await Future.delayed(const Duration(seconds: 2));
    throw Exception('Failed to fetch data'); // 模拟异常
  } catch (e) {
    debugPrint('Caught an exception: $e');
  }
}
 
void main() async {
  await fetchData();
  debugPrint('Done');
}

打印输出:

flutter: Caught an exception: Exception: Failed to fetch data
flutter: Done

4.2 catchError

catchError 方法需要一个回调函数,该函数接收异常作为参数。

import 'package:flutter/material.dart';
import 'dart:async';
 
Future<void> fetchData() {
  return Future.delayed(const Duration(seconds: 2))
      .then((_) {
        throw Exception('Failed to fetch data'); // 模拟异常
      })
      .catchError((e) {
        debugPrint('Caught an exception: $e');
      });
}
 
void main() async {
  await fetchData();
  debugPrint('Done');
}

打印输出:

flutter: Caught an exception: Exception: Failed to fetch data
flutter: Done

posted @ 2025-05-30 14:41  fengMisaka  阅读(116)  评论(0)    收藏  举报