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("关闭启动页,进入首页");
}
相关推荐
xiaoyan20153 小时前
基于flutter3.32+window_manager仿macOS/Wins风格桌面os系统
前端·flutter·dart
叽哥3 小时前
dart学习第 11 节: 空安全(下)—— 安全操作符详解
flutter·dart
weixin_4111918413 小时前
原生安卓与flutter混编的实现
android·flutter
会煮咖啡的猫1 天前
编写 Flutter 游戏摇杆组件
flutter
来来走走1 天前
Flutter dart运算符
android·前端·flutter
风清云淡_A1 天前
【Flutter3.8x】flutter从入门到实战基础教程(五):Material Icons图标的使用
前端·flutter
阳光明媚sunny1 天前
Flutter基础知识
flutter
新镜1 天前
【Flutter】双路视频播放方案
flutter·音视频
GeniuswongAir1 天前
flutter分享到支付宝
flutter