最全面的Flutter(dart)中future使用教程和异步原理
前言
接触过flutter的开发同学都知道,flutter是一个单线程模型的跨端UI框架,可以说是非常强大,主流的客户端、桌面、前端应用都可以通过flutter来实现,但是既然是写UI就有一个绕不开的话题,数据获取和UI渲染是如何处理的呢?像安卓和iOS都有自己的线程机制:子线程获取数据,main线程用于UI刷新,那Flutter 这个单线程是如何做到的呢?关于Flutter的UI刷新这里先不谈,先看看flutter中的异步操作是如何处理
Flutter 如何的异步操作
Dart 是一个单线程模型的编程语言,这意味着所有的 Dart 代码在同一个线程上运行。然而,Dart 提供了事件循环和异步编程机制来处理耗时任务,以便不会阻塞 UI。
事件循环和微任务队列:
事件循环: Dart 的事件循环负责管理事件队列和微任务队列。它按顺序处理队列中的任务。
事件队列: 包含 I/O 操作、计时器等任务。
微任务队列: 包含高优先级的任务,通常用于 Future 的回调。
事件循环会优先处理微任务队列,然后处理事件队列中的任务,因此想要提高事件的优先级可以通过定义微任务队列来实现
异步编程模型
Dart 提供了丰富的API来支持异步编程,包括 Future、Stream 以及 async/await 关键字。
1、Future
future 提供了丰富的方法来满足我们异步编程的场景:
1. then()
then() 方法用于在 Future 完成后执行回调函数。它接收一个参数,即 Future 的结果。
Future<String> fetchData() async {return Future.delayed(Duration(seconds: 2), () => 'Data fetched');
}void main() {fetchData().then((data) {print(data); // 输出: Data fetched});
}
2. catchError()
catchError() 用于捕获 Future 执行过程中抛出的错误。它可以与 then() 链式调用。
Future<String> fetchDataWithError() async {return Future.delayed(Duration(seconds: 2), () => throw Exception('Error'));
}void main() {fetchDataWithError().then((data) {print(data);}).catchError((error) {print('Caught error: $error'); // 输出: Caught error: Exception: Error});
}
补充:关于Future中onError() 和catchError():
catchError 和 onError 都是用于处理 Future 中的错误,但它们有一些区别:
catchError:
- catchError 是 Future 类的一个方法,用于捕获并处理 Future 中的错误
- 它可以接受一个 test 参数,用于指定捕获哪些类型的错误。
- catchError 会在 Future 链中捕获错误并处理,然后返回一个新的 Future。
Future<int> futureWithCatchError() {return Future<int>.error('An error occurred').catchError((error) {print('Caught error: $error');return -1; // 捕获到异常返回一个默认值或者处理这个异常});
}
onError:
- onError 是 Future 类的一个方法,用于在 then 方法中处理错误。
- 它是 then 方法的第二个参数,用于处理 Future 中的错误。
- onError 只会在 then 方法中捕获错误,并不会返回一个新的 Future。
Future<int> futureWithOnError() {return Future<int>.error('An error occurred').then((value) {print('Value: $value');return value;}, onError: (error) {print('Caught error: $error');return -1; });
}
catchError 是 Future 类的一个方法,用于捕获并处理 Future 中的错误,并返回一个新的 Future。
onError 是 then 方法的第二个参数,用于在 then 方法中处理错误,并不会返回一个新的 Future。
3. whenComplete()
whenComplete() 在 Future 完成(无论成功或失败)时执行回调。它不接收 Future 的结果或错误。
Future<String> fetchData() async {return Future.delayed(Duration(seconds: 2), () => 'Data fetched');
}void main() {fetchData().then((data) {print(data);}).whenComplete(() {print('Future completed'); // 可以用于Future是否执行完成的逻辑判断});
}
4. timeout()
timeout() 方法用于为 Future 设置超时时间。如果在指定时间内 Future 未完成,则抛出超时异常。
Future<String> fetchData() async {return Future.delayed(Duration(seconds: 3), () => 'Data fetched');
}void main() {fetchData().timeout(Duration(seconds: 2)).then((data) {print(data);}).catchError((error) {//可以用于防止异步操作阻塞,加超时的判断print('Caught timeout: $error'); // 输出: Caught timeout: TimeoutException after 0:00:02.000000: Future not completed});
}
5. Future.wait()
Future.wait() 接收一个 Future 列表,并返回一个新的 Future,它在所有传入的 Future 完成后完成。会等多个异步操作都执行完成才返回结果,**使用场景:**同时call多个接口判断是否多个接口都call完成。
Future<String> fetchData1() async {return Future.delayed(Duration(seconds: 2), () => 'Data 1');
}Future<String> fetchData2() async {return Future.delayed(Duration(seconds: 3), () => 'Data 2');
}void main() {Future.wait([fetchData1(), fetchData2()]).then((results) {print(results); // 输出: [Data 1, Data 2]});
}
6. Future.any()
Future.any() 接收一个 Future 列表,并返回第一个完成的 Future 的结果。
Future<String> fetchData1() async {return Future.delayed(Duration(seconds: 3), () => 'Data 1');
}Future<String> fetchData2() async {return Future.delayed(Duration(seconds: 2), () => 'Data 2');
}void main() {Future.any([fetchData1(), fetchData2()]).then((result) {print(result); // 输出: Data 2});
}
7.Future.value
当一个函数需要返回 Future,但你已经有了结果,可以使用 Future.value 包装结果,使其符合异步接口要求。
Future<String> getData() {// 已经有结果,不需要执行异步操作return Future.value('Immediate Data');
}void main() {getData().then((data) {print(data); // 输出: Immediate Data});
}
8.Future.delayed
Future.delayed 用于创建一个在指定延迟时间后完成的 Future。
Future<String> fetchData() {return Future.delayed(Duration(seconds: 2), () => 'Fetched Data');
}void main() {fetchData().then((data) {print(data); // 在2秒后输出: Fetched Data});
}
9.Future.forEach
Future.forEach 是 Dart 中一个实用的异步迭代工具。它用于对集合中的每个元素执行异步操作,并按顺序确保所有操作完成。它的工作方式类似于同步的 forEach 方法,但支持异步函数。
当你需要对集合中的每个元素执行异步操作,并确保这些操作按顺序完成时,Future.forEach 是非常合适的工具。
Future.forEach 的基本语法如下:
Future<void> Future.forEach<T>(Iterable<T> elements,//elements: 一个可迭代的集合,包含需要处理的元素。FutureOr<void> action(T element)//action: 一个函数,接收一个元素并对其执行异步操作。该函数可以返回一个 Future,也可以是同步操作。
)
假设我们有一个异步函数 fetchData,它模拟对每个数字的异步处理。我们将对一个数字列表中的每个数字按顺序进行处理。
Future<void> fetchData(int number) async {// 模拟异步操作,例如网络请求或文件读取await Future.delayed(Duration(seconds: 1));print('Processed $number');
}void main() async {List<int> numbers = [1, 2, 3, 4, 5];// 使用 Future.forEach 对列表中的每个数字进行异步处理await Future.forEach(numbers, (number) async {await fetchData(number);});print('All numbers processed');
}
输出结果,可以看到是按照顺序执行Future
Processed 1
Processed 2
Processed 3
Processed 4
Processed 5
All numbers processed
注意⚠️:如果在迭代过程中发生错误,Future.forEach 将返回一个失败的 Future,你可以通过 .catchError 或 try-catch 进行错误处理。
void main() async {List<int> numbers = [1, 2, 0, 4, 5];try {await Future.forEach(numbers, (number) async {if (number == 0) {throw Exception('Division by zero');}print('Processed ${10 / number}');});} catch (e) {print('Error: $e');}
}
10.Future.doWhile
Future.doWhile :它在满足特定条件时,重复执行异步操作。与传统的 do-while 循环类似,Future.doWhile 会首先执行操作,然后检查条件,如果条件为 true,则继续执行。
Future.doWhile 的基本语法如下:
//action: 一个返回 Future<bool> 或 bool 的函数。当函数返回 true 时,循环继续;当返回 false 时,循环终止。
Future<void> Future.doWhile(FutureOr<bool> action())
假设我们有一个异步操作 fetchData,它每次执行会模拟一个异步任务,直到某个条件为假时才停止。
import 'dart:async';int counter = 0;Future<bool> fetchData() async {await Future.delayed(Duration(seconds: 1));counter++;print('Fetched data $counter times');return counter < 5;
}void main() async {await Future.doWhile(() async {return await fetchData();});print('Completed all fetch operations');
}
fetchData 会被调用多次,直到 counter 达到 5。输出如下:
Fetched data 1 times
Fetched data 2 times
Fetched data 3 times
Fetched data 4 times
Fetched data 5 times
Completed all fetch operations
11.Future.microtask
Future.microtask 是 Dart 中一个用于创建微任务的构造方法。微任务是事件循环的一部分,用于执行高优先级任务。与普通的事件任务不同,微任务会在当前事件循环结束之前尽快执行。使用 Future.microtask 可以在当前事件循环结束之前安排一个任务,使其尽早执行。简而言之就是它的优先级会比其他事件高
Future.microtask 的基本用法如下:
//computation: 一个函数,返回一个值或 Future,表示要执行的任务。
Future<T> Future.microtask(FutureOr<T> computation())
Future.microtask 和普通 Future,在控制台的打印顺序。
void main() {print('Start of main');Future(() => print('Future 1'));Future.microtask(() => print('Microtask 1'));Future(() => print('Future 2'));Future.microtask(() => print('Microtask 2'));print('End of main');
}
预期输出
Start of main
End of main
Microtask 1
Microtask 2
Future 1
Future 2
首先执行同步代码,因此控制台先打印 Start of main 和 End of main。
微任务队列:
微任务是在当前事件循环中尽快执行的任务。
Future.microtask(() => print(‘Microtask 1’)) 和 Future.microtask(() => print(‘Microtask 2’)) 被添加到微任务队列中。
在同步代码执行完毕后,立即执行微任务队列中的任务,因此 Microtask 1 和 Microtask 2 先于普通 Future 执行。
事件任务队列:
普通的 Future 被添加到事件任务队列中。
在微任务队列中的任务执行完毕后,事件任务开始执行。
因此,Future 1 和 Future 2 在微任务执行完之后才执行
12.Future.sync
Future.sync :用于创建一个同步完成的 Future。这个方法特别适用于将同步代码包装成 Future,以便在异步上下文中使用。Future.sync 可以立即执行传入的函数,并将其结果包装在一个 Future 中。和Future.value() 用法类似
下面是一个使用 Future.sync 的简单示例,演示如何将同步代码转换为 Future:
void main() {print('Start');Future.sync(() {print('This is a synchronous task executed as a Future');return 'Result of sync task';}).then((result) {print(result);});print('End');
}
输出:
Start
This is a synchronous task executed as a Future
End
Result of sync task
需要注意: Future.sync中打印的方法:This is a synchronous task executed as a Future 仍然在优先级最高的同步代码执行的, 只有*return ‘Result of sync task’;*才会包装成异步
async 和 await
在 Flutter 中,async 和 await 是 Dart 语言中用于处理异步编程的关键字。它们帮助开发者以同步风格编写异步代码,使代码更易读和维护。
async 关键字
- 作用: 将一个函数标记为异步函数。
- 返回类型: 异步函数可以返回 Future 或 void。如果函数没有明确返回 Future,Dart 会自动将其包装在一个 Future 中。
- 使用场景: 在需要使用 await 关键字的函数上使用 async。
Future<void> fetchData() async {// 使用 await 关键字的函数必须标记为 asyncawait Future.delayed(Duration(seconds: 2));print('Data fetched');
}
await 关键字
- 作用: 暂停异步函数的执行,直到 Future 完成,并返回 Future 的结果。
- 使用条件: await 只能在标记为 async 的函数中使用。
- 效果: 使得异步操作看起来像同步操作,简化了异步代码的编写。
Future<void> main() async {print('Fetching data...');await fetchData(); // 暂停执行,直到 fetchData 中的 Future 完成print('Data fetched, continue processing');
}
async 和 await 结合使用,使得异步代码易于理解,并避免了回调地狱的问题
Future<String> fetchDataFromApi() async {// 模拟从 API 获取数据await Future.delayed(Duration(seconds: 2));return 'Data from API';
}Future<void> processData() async {print('Start processing');String data = await fetchDataFromApi(); // 等待数据获取完成print('Processing $data');
}void main() async {print('Start main');await processData().catchError((error, stackTrace) => null); // 等待 processData 完成print('End main');
}
注意⚠️
使用 await processData(); 方式很容易阻塞代码导致不再向下执行,如果没有对异常值处理的话,因此使用时切记小心。再后面尽量加上catchError
async 和async*区别
在 Dart 中,async 和 async* 都用于定义异步函数,但它们有不同的用途和行为,特别是在处理异步流的时候。下面是它们的区别:
async
- 作用: 将一个函数标记为异步函数。
- 返回类型: 返回一个 Future,表示异步操作的结果。
- 使用场景: 当你需要执行异步操作并返回单个结果时,使用 async。
- 与 await 一起使用: 可以使用 await 来暂停函数的执行,直到某个 Future 完成。
Future<int> fetchNumber() async {await Future.delayed(Duration(seconds: 1)); // 模拟异步操作return 42; // 返回结果
}void main() async {int result = await fetchNumber();print(result); // 输出: 42
}
async*
- 作用: 将一个函数标记为异步生成器函数。
- 返回类型: 返回一个 Stream,表示一系列异步结果。
- 使用场景: 当你需要生成多个异步结果并在时间上分开它们时,使用 async*。
- 与 yield 一起使用: 使用 yield 来逐步生成多个值,并将它们发送到流中。
- 与 yield* 一起使用: 使用 yield* 可以将另一个生成器函数的输出合并到当前生成器中。
async*和yield 使用:
Stream<int> countStream() async* {for (int i = 1; i <= 3; i++) {await Future.delayed(Duration(seconds: 1)); // 模拟异步操作yield i; // 逐步生成值并发送到流中}
}void main() async {await for (int value in countStream()) {print('Received: $value'); // 输出: 1, 2, 3,每秒一个}
}
async和yield 使用:
// 一个简单的生成器函数,生成 1 到 3
Stream<int> numberStream() async* {for (int i = 1; i <= 3; i++) {await Future.delayed(Duration(seconds: 1)); // 模拟异步操作yield i; // 生成数字}
}// 使用 yield* 来将 numberStream 的输出合并到另一个流中
Stream<int> combinedStream() async* {yield* numberStream(); // 将 numberStream 的所有值传递到当前流中// 可以继续生成更多的值for (int i = 4; i <= 5; i++) {await Future.delayed(Duration(seconds: 1));yield i;}
}void main() async {await for (int value in combinedStream()) {print('Received: $value'); // 输出: 1, 2, 3, 4, 5}
}
numberStream:使用 async* 来定义一个异步生成器。使用 yield 来逐步生成数字 1 到 3。
combinedStream:使用 yield* numberStream() 将 numberStream 的输出合并到 combinedStream 中。在 yield* 之后,可以继续使用 yield 生成更多的值。
主函数:使用 await for 来消费 combinedStream,逐个接收流中的值并打印。
end
以上便是Future的全部用法解析,理解其含义,便可以结合自己的业务场景灵活使用。