在现代应用程序开发中,异步操作无处不在,例如网络请求、文件读写、数据库查询等。这些操作通常需要一定的时间才能完成,如果同步执行,会导致 UI 阻塞,用户体验下降。Dart 提供了强大的异步编程支持,主要通过 Future 对象和 async/await 关键字来处理异步操作,从而避免 UI 阻塞,提高应用程序的响应性。
1. Future (未来)
Future 对象代表一个异步操作的最终结果或错误。当一个异步操作开始时,它会立即返回一个 Future 对象。这个 Future 对象在操作完成时会“完成”并带有一个值(成功)或一个错误(失败)。
创建 Future:
Future.value(value):创建一个立即完成并带有指定值的 Future。Future.error(error):创建一个立即完成并带有指定错误的 Future。Future.delayed(duration, [computation]):创建一个在指定延迟后完成的 Future。处理 Future:
then():当 Future 成功完成时,then() 方法会执行其回调函数,并接收 Future 的结果。catchError():当 Future 失败并抛出错误时,catchError() 方法会执行其回调函数,并接收错误对象。whenComplete():无论 Future 成功或失败,whenComplete() 方法都会执行其回调函数。通常用于清理资源。void main() {
print("Start of main");
// 模拟一个异步操作,2秒后返回一个字符串
Future<String> fetchData() {
return Future.delayed(Duration(seconds: 2), () {
// throw Exception("数据加载失败!"); // 模拟错误
return "Data fetched successfully!";
});
}
fetchData().then((data) {
print("Success: $data");
}).catchError((error) {
print("Error: $error");
}).whenComplete(() {
print("Fetch data operation completed.");
});
print("End of main");
}
// 预期输出:
// Start of main
// End of main
// (2秒后)
// Success: Data fetched successfully!
// Fetch data operation completed.
2. async 和 await 关键字
async 和 await 关键字提供了一种更简洁、更易读的方式来编写异步代码,使其看起来像同步代码。
async:用于标记一个函数是异步的。一个 async 函数总是返回一个 Future。await:只能在 async 函数内部使用。它会暂停 async 函数的执行,直到其后面的 Future 完成。一旦 Future 完成,await 表达式的值就是 Future 的结果。如果 Future 失败,await 会抛出错误。void main() async {
print("Start of main");
// 模拟一个异步操作
Future<String> fetchData() {
return Future.delayed(Duration(seconds: 2), () {
return "Data fetched successfully!";
});
}
try {
String data = await fetchData(); // 等待 fetchData 完成
print("Success: $data");
} catch (error) {
print("Error: $error");
} finally {
print("Fetch data operation completed.");
}
print("End of main");
}
// 预期输出:
// Start of main
// (2秒后)
// Success: Data fetched successfully!
// Fetch data operation completed.
// End of main
async/await 的优势:
try-catch-finally 块来处理异步操作中的错误。3. 事件循环 (Event Loop)
Dart 是单线程的,但它通过事件循环(Event Loop)和两个队列(事件队列和微任务队列)来实现并发。当 Dart 程序启动时,它会启动一个事件循环。
Future.then() 的回调。微任务队列的优先级高于事件队列。在事件循环从事件队列中取出下一个事件之前,它会清空微任务队列。理解事件循环有助于理解异步代码的执行顺序和性能。
异步编程在 Flutter 开发中至关重要,因为大多数与外部资源(如网络、文件系统、数据库)的交互都是异步的。Future 和 async/await 是 Flutter 应用中处理这些异步操作的基石,它们确保 UI 保持响应,提供流畅的用户体验。
案例:从网络加载图片并显示加载状态
我们将创建一个 Flutter 应用,从网络加载一张图片,并在图片加载过程中显示加载指示器,加载成功后显示图片,加载失败则显示错误信息。这个案例将演示 Future、async/await 和 FutureBuilder 的结合使用。
import 'package:flutter/material.dart';
class ImageLoaderScreen extends StatefulWidget {
const ImageLoaderScreen({super.key});
@override
State<ImageLoaderScreen> createState() => _ImageLoaderScreenState();
}
class _ImageLoaderScreenState extends State<ImageLoaderScreen> {
final String imageUrl = 'https://picsum.photos/id/237/200/300'; // 示例图片 URL
// final String imageUrl = 'https://invalid.url/image.jpg'; // 模拟加载失败
Future<void> _loadImage() async {
// 模拟网络请求延迟
await Future.delayed(const Duration(seconds: 2));
// 在实际应用中,这里会使用 http 包进行网络请求
// 例如:final response = await http.get(Uri.parse(imageUrl));
// if (response.statusCode == 200) { return true; } else { throw Exception('Failed to load image'); }
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('异步图片加载'),
),
body: Center(
child: FutureBuilder<void>(
future: _loadImage(), // 传入一个 Future 对象
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
// 如果 Future 仍在等待中,显示加载指示器
return const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 16),
Text('图片加载中...'),
],
);
} else if (snapshot.hasError) {
// 如果 Future 完成并带有错误,显示错误信息
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error, color: Colors.red, size: 60),
const SizedBox(height: 16),
Text('加载失败: ${snapshot.error}', textAlign: TextAlign.center),
ElevatedButton(
onPressed: () {
setState(() {
// 重新触发 Future,再次尝试加载
});
},
child: const Text('重试'),
),
],
);
} else {
// 如果 Future 成功完成,显示图片
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.network(
imageUrl,
width: 200,
height: 300,
fit: BoxFit.cover,
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) {
return const Text('图片显示失败');
},
),
const SizedBox(height: 16),
const Text('图片加载成功!'),
],
);
}
},
),
),
);
}
}
void main() {
runApp(const MaterialApp(home: ImageLoaderScreen()));
}
案例分析:
Future<void> _loadImage() async { ... }:这是一个异步函数,模拟了从网络加载图片的过程。await Future.delayed(...) 模拟了网络请求的延迟。在实际应用中,这里会使用 http 或 dio 等网络库进行真正的网络请求。FutureBuilder<void>(future: _loadImage(), builder: ...):FutureBuilder 是 Flutter 中用于处理 Future 的强大 Widget。它接收一个 Future 对象作为 future 参数,并根据 Future 的当前状态(connectionState)来构建不同的 UI。snapshot.connectionState == ConnectionState.waiting:当 _loadImage() 还在执行中时,FutureBuilder 会进入 waiting 状态,此时我们显示 CircularProgressIndicator 和“图片加载中...”的文本。snapshot.hasError:如果 _loadImage() 抛出错误(例如网络请求失败),FutureBuilder 会进入 hasError 状态,此时我们显示错误图标和错误信息,并提供“重试”按钮。snapshot.hasData:如果 _loadImage() 成功完成(尽管这里返回 void,但表示操作成功),FutureBuilder 会进入 hasData 状态,此时我们显示加载成功的图片和文本。Image.network(imageUrl, loadingBuilder: ..., errorBuilder: ...):Image.network 本身也支持异步加载图片,并提供了 loadingBuilder 和 errorBuilder 回调,用于在图片加载中和加载失败时显示不同的 UI。这与 FutureBuilder 的逻辑类似,但更专注于图片加载本身。这个案例清晰地展示了 Dart 异步编程在 Flutter 应用中的实际应用。通过 Future 和 async/await,结合 FutureBuilder 等 Flutter 提供的异步 UI 组件,我们可以优雅地处理耗时操作,确保应用程序的流畅性和响应性,从而提供更好的用户体验。