在现代应用程序开发中,异步操作是不可避免的。例如,网络请求、文件读写、数据库操作等都可能需要较长时间才能完成。如果在主线程(UI 线程)中执行这些耗时操作,会导致 UI 阻塞,用户界面无响应,从而影响用户体验。Dart 语言提供了强大的异步编程支持,主要通过 Future 对象和 async/await 关键字来实现。
1. Future 对象
Future 对象代表一个异步操作的最终结果或错误。当一个异步操作开始时,它会立即返回一个 Future 对象。这个 Future 对象在操作完成时会“完成”(complete),并携带一个值(成功结果)或一个错误(失败结果)。
then() 方法:用于注册一个回调函数,当 Future 完成并携带值时执行。then() 方法本身也会返回一个新的 Future,可以链式调用。catchError() 方法:用于注册一个错误处理回调函数,当 Future 完成并携带错误时执行。whenComplete() 方法:无论 Future 成功还是失败,都会执行的回调函数,通常用于清理资源。Future<String> fetchUserData() {
return Future.delayed(Duration(seconds: 2), () {
// 模拟网络请求延迟
// throw Exception("网络请求失败"); // 模拟错误
return "User Data Loaded";
});
}
void main() {
print("开始获取用户数据...");
fetchUserData().then((data) {
print("数据获取成功: $data");
}).catchError((error) {
print("数据获取失败: $error");
}).whenComplete(() {
print("数据获取操作完成。");
});
print("继续执行其他任务...");
}
// 预期输出:
// 开始获取用户数据...
// 继续执行其他任务...
// (2秒后)
// 数据获取成功: User Data Loaded
// 数据获取操作完成。
2. async 和 await 关键字
async 和 await 是 Dart 异步编程的语法糖,它们使得异步代码看起来和同步代码一样,大大提高了可读性和可维护性。
async:用于标记一个函数是异步的。一个 async 函数总是返回一个 Future 对象。如果 async 函数没有显式返回 Future,Dart 会自动将其返回值包装在一个 Future 中。await:只能在 async 函数内部使用。它会暂停当前 async 函数的执行,直到 await 后面的 Future 完成。一旦 Future 完成,await 表达式的值就是 Future 完成时携带的值。如果 Future 完成时携带错误,await 表达式会抛出该错误。Future<String> downloadFile(String url) async {
print("开始下载文件: $url");
await Future.delayed(Duration(seconds: 3)); // 模拟下载延迟
// throw Exception("下载失败"); // 模拟下载失败
print("文件下载完成: $url");
return "File Content from $url";
}
Future<void> processData() async {
try {
String content1 = await downloadFile("http://example.com/file1.txt");
print("处理内容1: $content1");
String content2 = await downloadFile("http://example.com/file2.txt");
print("处理内容2: $content2");
print("所有文件处理完毕。");
} catch (e) {
print("处理数据时发生错误: $e");
}
}
void main() {
print("主程序开始。");
processData();
print("主程序继续执行,不等待异步操作。");
}
// 预期输出:
// 主程序开始。
// 开始下载文件: http://example.com/file1.txt
// 主程序继续执行,不等待异步操作。
// (3秒后)
// 文件下载完成: http://example.com/file1.txt
// 处理内容1: File Content from http://example.com/file1.txt
// 开始下载文件: http://example.com/file2.txt
// (3秒后)
// 文件下载完成: http://example.com/file2.txt
// 处理内容2: File Content from http://example.com/file2.txt
// 所有文件处理完毕。
3. Future.wait()
当需要等待多个 Future 都完成时,可以使用 Future.wait()。它接收一个 Future 列表,并返回一个新的 Future,当列表中所有 Future 都成功完成时,这个新的 Future 才会完成,并携带一个包含所有结果的列表。如果其中任何一个 Future 失败,Future.wait() 也会立即失败。
Future<String> fetchAsset(String assetName, int delaySeconds) {
return Future.delayed(Duration(seconds: delaySeconds), () => "Loaded $assetName");
}
void main() async {
print("开始加载所有资源...");
try {
List<String> results = await Future.wait([
fetchAsset("Image", 2),
fetchAsset("Audio", 1),
fetchAsset("Video", 3),
]);
print("所有资源加载完成: $results");
} catch (e) {
print("加载资源时发生错误: $e");
}
print("主程序结束。");
}
// 预期输出:
// 开始加载所有资源...
// (3秒后)
// 所有资源加载完成: [Loaded Image, Loaded Audio, Loaded Video]
// 主程序结束。
异步编程在 Flutter 应用中至关重要,因为大多数实际应用都需要与网络、文件系统或数据库进行交互,这些都是耗时操作。Future 和 async/await 是 Flutter 中处理这些操作的标准方式,它们确保了 UI 的流畅性和响应性。
案例:从网络加载数据并显示
我们将创建一个 Flutter 应用,从一个模拟的网络 API 加载用户列表,并在加载完成后显示在界面上。这个案例将演示 async/await 在网络请求和 UI 更新中的应用。
import 'package:flutter/material.dart';
import 'dart:convert'; // 用于 JSON 解码
import 'package:http/http.dart' as http; // 引入 http 包,需要在 pubspec.yaml 中添加依赖
// 模拟的用户数据模型
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'],
name: json['name'],
email: json['email'],
);
}
}
class UserListScreen extends StatefulWidget {
const UserListScreen({super.key});
@override
State<UserListScreen> createState() => _UserListScreenState();
}
class _UserListScreenState extends State<UserListScreen> {
List<User> _users = [];
bool _isLoading = false;
String? _error;
@override
void initState() {
super.initState();
_fetchUsers(); // 页面初始化时加载用户数据
}
Future<void> _fetchUsers() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
// 模拟网络请求,使用一个公共的测试 API
final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
if (response.statusCode == 200) {
// 请求成功,解析 JSON 数据
List<dynamic> userJson = jsonDecode(response.body);
setState(() {
_users = userJson.map((json) => User.fromJson(json)).toList();
_isLoading = false;
});
} else {
// 请求失败
setState(() {
_error = 'Failed to load users: ${response.statusCode}';
_isLoading = false;
});
}
} catch (e) {
// 捕获网络错误或其他异常
setState(() {
_error = 'Error fetching users: $e';
_isLoading = false;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('用户列表'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _isLoading ? null : _fetchUsers, // 正在加载时禁用刷新按钮
),
],
),
body: _isLoading
? const Center(child: CircularProgressIndicator()) // 加载中显示进度指示器
: _error != null
? Center(child: Text('错误: $_error')) // 显示错误信息
: ListView.builder(
itemCount: _users.length,
itemBuilder: (context, index) {
final user = _users[index];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: ListTile(
leading: CircleAvatar(child: Text(user.id.toString())),
title: Text(user.name),
subtitle: Text(user.email),
),
);
},
),
);
}
}
void main() {
runApp(const MaterialApp(home: UserListScreen()));
}
案例分析:
Future<void> _fetchUsers() async { ... }:_fetchUsers 函数被标记为 async,这意味着它是一个异步函数,并且会返回一个 Future<void>。函数内部可以使用 await 关键字。await http.get(Uri.parse('...')):这里使用了 await 关键字来等待 http.get 方法返回的 Future 完成。http.get 是一个异步操作,它会发送一个 HTTP GET 请求并等待响应。await 会暂停 _fetchUsers 函数的执行,直到网络请求完成并返回响应。try { ... } catch (e) { ... }:异步操作中经常会发生错误(如网络连接失败、服务器返回错误状态码)。使用 try-catch 块可以捕获这些错误,从而避免应用崩溃,并向用户显示友好的错误信息。setState(() { _isLoading = true; ... });:在异步操作开始前,我们通过 setState 更新 _isLoading 状态为 true,从而在 UI 上显示加载指示器。当操作完成后(无论成功或失败),再次调用 setState 更新 _isLoading 为 false,并显示数据或错误信息。User.fromJson(Map<String, dynamic> json):这是一个工厂构造函数,用于将 JSON 数据解析成 User 对象。在处理网络数据时,通常需要将原始的 JSON 字符串转换为 Dart 对象,以便于操作。CircularProgressIndicator():在数据加载过程中,我们显示一个圆形进度指示器,提升用户体验。这个案例清晰地展示了 Future 和 async/await 在 Flutter 应用中处理网络请求的关键作用。通过它们,我们可以编写出非阻塞、响应迅速且易于理解的异步代码,从而构建出高性能和用户友好的移动应用程序。
注意: 运行此案例需要在 pubspec.yaml 文件中添加 http 依赖:
dependencies:
flutter:
sdk: flutter
http: ^1.2.1 # 添加此行
然后运行 flutter pub get 获取依赖。