dart学习第 13 节:异步编程基础 —— Future 与 async/await

在前面的学习中,我们写的代码几乎都是同步执行 的 ------ 代码从上到下依次运行,前一行执行完才会执行下一行。但在实际开发中,很多操作(如网络请求、文件读写)需要等待外部响应,若用同步方式处理会导致程序 "卡死"。今天我们要学习的异步编程,就是解决这类问题的核心技术,尤其在 UI 程序(如 Flutter 应用)中至关重要。

一、同步 vs 异步:为什么 UI 程序离不开异步?

1. 同步执行的局限性

同步代码的执行流程像一条直线,每一步必须等待上一步完成

dart 复制代码
void main() {
  print("开始执行");
  print("执行耗时操作...");
  // 模拟一个耗时 3 秒的操作(如网络请求)
  for (int i = 0; i < 1000000000; i++) {}
  print("操作完成");
  print("继续执行其他任务");
}

// 输出顺序:
// 开始执行
// 执行耗时操作...
// (等待 3 秒)
// 操作完成
// 继续执行其他任务

这种模式在耗时操作(如网络请求、大数据处理)面前会出现严重问题:

  • 整个程序会被 "阻塞",无法响应用户操作(如点击按钮、滑动屏幕)
  • UI 会冻结,给用户带来 "卡死" 的糟糕体验

2. 异步执行的优势

异步代码的核心是:耗时操作在 "后台" 执行,主线程继续处理其他任务,待耗时操作完成后再回调处理结果

用生活类比:

  • 同步:烧开水时一直盯着水壶,什么也不做,水开了再倒水泡茶。
  • 异步:烧开水时去准备茶叶和杯子,水开了再回来倒水泡茶。

在 UI 程序中,异步是刚需:

  • 保证 UI 线程不被阻塞,始终能响应用户操作
  • 提升程序运行效率,多个任务可 "并行" 处理

二、Future:异步操作的 "占位符"

Dart 中用 Future 表示一个 "未来会完成的操作",它是异步操作的 "占位符"------ 在创建时我们不知道结果,但知道未来某个时刻会得到结果(成功或失败)。

1. Future 的三种状态

  • 未完成(pending) :异步操作正在执行,结果尚未产生
  • 已完成(completed with value) :异步操作成功,返回结果值
  • 已完成(completed with error) :异步操作失败,返回错误信息

2. 创建 Future 与处理结果

通过 Future 构造函数创建异步操作,并用 then 处理成功结果,catchError 处理错误:

dart 复制代码
void main() {
  print("开始执行主线程任务");

  // 创建一个 Future(异步操作)
  Future<String> fetchData() {
    // 模拟网络请求,2 秒后返回结果
    return Future.delayed(Duration(seconds: 2), () {
      // 模拟成功场景
      return "获取到的数据:Dart 异步编程";

      // 模拟失败场景(取消上面一行注释,启用下面一行)
      // throw Exception("网络错误,获取数据失败");
    });
  }

  // 调用异步函数,得到 Future 对象
  Future<String> future = fetchData();

  // 注册回调:当 Future 成功完成时执行
  future
      .then((data) {
        print("异步操作成功:$data");
      })
      .catchError((error) {
        // 注册回调:当 Future 失败时执行
        print("异步操作失败:${error.toString()}");
      })
      .whenComplete(() {
        // 注册回调:无论成功或失败,最终都会执行
        print("异步操作结束(无论成败)");
      });

  print("主线程继续执行其他任务");
}

// 输出顺序(成功场景):
// 开始执行主线程任务
// 主线程继续执行其他任务
// (等待 2 秒)
// 异步操作成功:获取到的数据:Dart 异步编程
// 异步操作结束(无论成败)

关键特点:

  • 调用 fetchData() 后,主线程不会等待,直接执行下一行 print
  • 2 秒后异步操作完成,才会执行 then 中的回调函数
  • catchError 捕获异步操作中抛出的错误
  • whenComplete 类似 "finally",无论成败都会执行

3. Future.value 与 Future.error

快速创建 "已完成" 的 Future:

dart 复制代码
void main() {
  // 直接创建一个成功的 Future
  Future.value("直接返回成功结果").then((data) {
    print(data); // 输出:直接返回成功结果
  });

  // 直接创建一个失败的 Future
  Future.error(Exception("直接返回错误")).catchError((error) {
    print(error); // 输出:Exception: 直接返回错误
  });
}

三、async/await:用同步风格写异步代码(重点)

虽然 then 链式调用可以处理异步,但多层嵌套时会导致 "回调地狱"(代码臃肿、难以维护)。Dart 提供了 async/await 语法糖,让我们能用同步代码的写法来编写异步逻辑。

1. 基本用法

  • async:修饰函数,表明该函数是异步的,返回值会自动包装为 Future
  • await:只能在 async 函数中使用,等待 Future 完成并获取结果
dart 复制代码
void main() {
  print("开始执行主线程任务");

  // 调用异步函数(无需等待,主线程继续执行)
  fetchAndPrintData();

  print("主线程继续执行其他任务");
}

// 用 async 修饰,表明这是异步函数
Future<void> fetchAndPrintData() async {
  try {
    print("开始异步获取数据");

    // 用 await 等待 Future 完成,获取结果(类似同步代码的写法)
    String data = await fetchData(); // 等待 fetchData() 完成

    // 上面的 await 会"暂停"当前函数,直到 Future 完成
    print("异步操作成功:$data");
  } catch (error) {
    // 捕获异步操作中的错误(替代 catchError)
    print("异步操作失败:${error.toString()}");
  } finally {
    // 无论成功失败都会执行(替代 whenComplete)
    print("异步操作结束(无论成败)");
  }
}

// 模拟网络请求的异步函数
Future<String> fetchData() {
  return Future.delayed(Duration(seconds: 2), () {
    return "获取到的数据:async/await 真方便";
    // 模拟失败:throw Exception("网络错误");
  });
}

// 输出顺序:
// 开始执行主线程任务
// 主线程继续执行其他任务
// 开始异步获取数据
// (等待 2 秒)
// 异步操作成功:获取到的数据:async/await 真方便
// 异步操作结束(无论成败)

2. 为什么 async 函数返回 Future

  • async 修饰的函数,其返回值会被自动包装为 Future
  • 若函数返回 int,实际返回类型是 Future<int>
  • 若函数无返回值,实际返回类型是 Future<void>
dart 复制代码
// 返回 Future<int>
Future<int> calculate() async {
  await Future.delayed(Duration(seconds: 1));
  return 100; // 自动包装为 Future.value(100)
}

void main() async {
  int result = await calculate(); // 用 await 获取结果
  print(result); // 输出:100
}

3. 处理多个异步操作

async/await 让多个异步操作的顺序执行变得简单:

dart 复制代码
// 模拟三个异步操作
Future<String> fetchUser() =>
    Future.delayed(Duration(seconds: 1), () => "用户信息");
Future<String> fetchOrders() =>
    Future.delayed(Duration(seconds: 1), () => "订单列表");
Future<String> fetchRecommendations() =>
    Future.delayed(Duration(seconds: 1), () => "推荐商品");

// 顺序执行多个异步操作
Future<void> fetchAllData() async {
  print("开始获取所有数据...");

  // 按顺序执行,总耗时 ~3 秒
  String user = await fetchUser();
  print("获取到:$user");

  String orders = await fetchOrders();
  print("获取到:$orders");

  String recommendations = await fetchRecommendations();
  print("获取到:$recommendations");

  print("所有数据获取完成");
}

void main() {
  fetchAllData();
}

若多个异步操作无依赖关系,可并行执行(用 Future.wait):

dart 复制代码
Future<void> fetchAllDataParallel() async {
  print("开始并行获取所有数据...");

  // 并行执行,总耗时 ~1 秒(取最长的单个操作时间)
  List<Future<String>> futures = [
    fetchUser(),
    fetchOrders(),
    fetchRecommendations(),
  ];

  // 等待所有 Future 完成,返回结果列表
  List<String> results = await Future.wait(futures);

  for (String result in results) {
    print("获取到:$result");
  }

  print("所有数据获取完成");
}

四、异步编程常见陷阱

1. 忘记使用 await

dart 复制代码
Future<int> getNumber() async => 42;

void main() async {
  // 错误:忘记用 await,得到的是 Future 而不是结果
  var result = getNumber();
  print(result); // 输出:Instance of 'Future<int>'

  // 正确:用 await 获取结果
  var correctResult = await getNumber();
  print(correctResult); // 输出:42
}

2. 在非 async 函数中使用 await

dart 复制代码
// 错误:await 只能在 async 函数中使用
void badFunction() {
  // await Future.delayed(Duration(seconds: 1)); // 编译错误
}

// 正确:用 async 修饰函数
void goodFunction() async {
  await Future.delayed(Duration(seconds: 1)); // 正确
}

3. 未处理异常导致程序崩溃

dart 复制代码
Future<void> riskyOperation() async {
  throw Exception("意外错误"); // 抛出异常
}

void main() async {
  // 错误:未处理异常,会导致程序崩溃
  // await riskyOperation();

  // 正确:用 try-catch 处理异常
  try {
    await riskyOperation();
  } catch (e) {
    print("捕获到异常:$e"); // 安全处理
  }
}

五、实际应用场景

异步编程在实际开发中无处不在:

网络请求

dart 复制代码
Future<User> fetchUserInfo(String userId) async {
  final response = await http.get(
    Uri.parse("https://api.example.com/users/$userId"),
  );
  if (response.statusCode == 200) {
    return User.fromJson(json.decode(response.body));
  } else {
    throw Exception("Failed to load user");
  }
}

本地存储操作

dart 复制代码
Future<void> saveData(String key, String value) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString(key, value);
}

延迟执行

dart 复制代码
Future<void> showSplashScreen() async {
  print("显示启动页");
  await Future.delayed(Duration(seconds: 3)); // 等待 3 秒
  print("关闭启动页,进入首页");
}
相关推荐
奋斗的小青年!!10 小时前
Flutter浮动按钮在OpenHarmony平台的实践经验
flutter·harmonyos·鸿蒙
程序员老刘13 小时前
一杯奶茶钱,PicGo + 阿里云 OSS 搭建永久稳定的个人图床
flutter·markdown
奋斗的小青年!!16 小时前
OpenHarmony Flutter 拖拽排序组件性能优化与跨平台适配指南
flutter·harmonyos·鸿蒙
小雨下雨的雨18 小时前
Flutter 框架跨平台鸿蒙开发 —— Stack 控件之三维层叠艺术
flutter·华为·harmonyos
行者9619 小时前
OpenHarmony平台Flutter手风琴菜单组件的跨平台适配实践
flutter·harmonyos·鸿蒙
小雨下雨的雨20 小时前
Flutter 框架跨平台鸿蒙开发 —— Flex 控件之响应式弹性布局
flutter·ui·华为·harmonyos·鸿蒙系统
cn_mengbei20 小时前
Flutter for OpenHarmony 实战:CheckboxListTile 复选框列表项详解
flutter
cn_mengbei20 小时前
Flutter for OpenHarmony 实战:Switch 开关按钮详解
flutter
奋斗的小青年!!20 小时前
OpenHarmony Flutter实战:打造高性能订单确认流程步骤条
flutter·harmonyos·鸿蒙
Coder_Boy_21 小时前
Flutter基础介绍-跨平台移动应用开发框架
spring boot·flutter