当前位置: 首页 > news >正文

最全面的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的全部用法解析,理解其含义,便可以结合自己的业务场景灵活使用。


http://www.mrgr.cn/news/69208.html

相关文章:

  • Docker 命令大全
  • 深度学习基础—Beam search集束搜索
  • python习题练习
  • Homebrew 命令大全
  • 【人工智能】langchain应用的简单QA流程链路
  • 【苍穹外卖】学习日志-day1
  • 硬件---3电容---电容特性、上电和断电延时、稳压功能、容抗计算
  • 【数据集】【YOLO】【目标检测】树木倒塌识别数据集 9957 张,YOLO道路树木断裂识别算法实战训练教程!
  • Springboot中的单元测试该如何进行?
  • 【前端】Svelte:响应性声明
  • CODESYS V3.5工程信息查看器
  • 【数学二】线性代数-向量-正交规范化、正交矩阵
  • 羲和数据集收集器0.9
  • 探索App Intents:让你的应用与Siri无缝互动的新方式
  • 【linux系统】Linux系统调优的方法与技巧
  • 派对鹦鹉—Party Parrot
  • 【Windows】CMD命令学习——快捷键
  • 鸿蒙next版开发:ArkTS组件鼠标事件详解
  • 书生实战营第四期-基础岛第四关-InternLM + LlamaIndex RAG 实践
  • 【单片机程序】详解IO输入输出方向设置寄存器写法程序来自定义更换引脚与迁移参考程序到工程的流程
  • Google SERP API 对接说明
  • 2.索引:SQL 性能分析详解
  • 公司文件防止泄密的方式(如何防止技术泄密)?5个防泄密措施,请谨记!
  • 操作系统(11) (POSIX--Linux线程编程---Mutex互斥锁语法应用)
  • 临床检验方法与仪器 第一部分作业:光谱分析仪器与技术的总结与归纳+新型光谱仪的调研
  • Python学习从0到1 day26 第三阶段 Spark ① 数据输入