什么是Future
Future是Dart处理异步的API。
异步方法
如果你的程序中有两个方法A 和B,方法A 在方法B之前执行,方法A执行需要五秒。
如果A方法是同步的,B方法会等A方法执行结束才会被调用,也就是说B方法五秒之后才会被执行。
如果A方法是异步的,程序在执行方法A时,发现A是一个异步方法,程序会将A放入待处理事件的末尾,接着执行B方法,也就是说B方法无须等待A方法执行完。
在客户端异步是非常有用的,如果你在初始化时有一个非常耗时,但又不需要它在ui画面响应前就执行完成的方法,你就可以使用异步。
下图展示了同步和异步的区别及Dart如何使用单线程处理异步。
如何创建Future方法
使用Future的构造方法
Future
Future构造函数的参数为Function(函数)类型,将你需要异步执行的代码写到该参数里。
Future(FutureOr<T> computation())
delayed
Future.delayed构造函数主要用于创建一个延迟一段时间执行的Future 。
该构造函数有两个参数:
duration参数来控制延迟多长时间 ;
Function类型的参数是需要异步执行的函数;
Future.delayed(Duration duration, [FutureOr<T> computation()])
microtask
Future.microtask构造函数是通过微任务队列处理的。
Future.microtask构造函数的参数为Function(函数)类型,将你需要异步执行的代码写到该参数里。
Future.microtask(FutureOr<T> computation())
PS:如果你不太理解微任务队列,这是Dart用来处理事件的队列之一,你可以在学习完本文后,了解一下作者的另一篇文章Dart进阶——一文学会Dart事件循环和异步调用。
sync
立即返回结果的Future。
Future.sync构造函数的参数为Function(函数)类型,将你需要异步执行的代码写到该参数里。
Future.sync(FutureOr<T> computation())
四种构造方法的处理优先级
一段代码示例帮助你理解Future类四种构造方法的处理优先级。
Future(() => print('Future异步方法'));
Future.delayed(Duration(seconds: 3), () => print('delayed异步方法'));
Future.microtask(() => print('microtask异步方法'));
Future.sync(() => print('sync异步方法'));
控制台输出如下。
sync异步方法 microtask异步方法 Future异步方法 delayed异步方法
四种构造方法的处理优先级:
sync > microtask > Future = delayed
自定义返回值为Future的方法
创建一个方法,将方法的返回值声明为Future,将需要异步执行的代码写到方法返回的Future的构造函数里。
一定记住,只有写在Future构造函数里的代码才是异步的。
Future testFuture() {
//写在这里的方法是同步的
return Future(() {//写在这里的代码是异步的});
}
使用此方法的意义
你可能会有些疑问,我直接使用Future的构造方法就可以了,为什么还要这么写,这不是多此一举吗?
我会用两段代码给你介绍一下上述方式的作用。
使用Future构造方法创建的代码:
void main(List<String> arguments) {
Future(() {
for (var i = 0; i < 1000000000; i++) {}
print('耗时任务结束');
});
print('main函数上的代码');
}
当你出于某些代码健壮性上的考虑,希望将你的异步代码从main函数中抽离出来,成为独立的方法的同时又能方便的进行Future的链式调用,上面提到的将方法返回值声明为Future的方式就有了用武之地。
将方法返回值声明为Future的方式创建的代码:
void main(List<String> arguments) {
testFuture();
print('main函数上的代码');
}
Future testFuture() {
return Future(() {
for (var i = 0; i < 1000000000; i++) {}
print('耗时任务结束');
});
}
如果你想指定Future构造函数的返回值类型,你可以这样写
这里泛型约束的值是调用then方法时的value
Future<String> testFutureString() {
return Future(() => "String类型数据");
}
可以使用这种方式声明一个Future构造函数无返回值的Future方法
Future<void> testFutureVoid() {
return Future(() {});
}
使用async关键字
async是Dart官方用于简化Future的关键字,我们使用async关键字实现和上面代码一样的功能。
Future<String> testFutureString() async {
return "String类型数据";
}
Future类的常用方法
then
then方法用来注册Future完成时要调用的回调函数。
then方法的默认参数为Function,该方法的参数为Future的返回值。
then方法的另一个参数是onError,当调用then方法的Future出现异常时调用onError,onError方法的参数为dynamice, StackTrace stack。
then方法的返回值为Future,可以很方便的进行链式调用。
代码示例:
Future(() {
return 'future函数的返回值';
}).then((value) => print(value));
控制台输出如下
future函数的返回值
代码示例:
Future(() {
throw StateError('This is a Dart exception in Future.');
}).then((value) => print(value), onError: (dynamic e, StackTrace stack) => print(e));
Bad state: This is a Dart exception in Future.
catchError
catchError用来处理调用它的Future出现的异常。
catchError方法的默认参数与then的onError参数一致,catchError方法的参数为dynamic e, StackTrace stack。
catchError方法的另一个参数是test,返回值限定为bool类型的函数,该函数参数为异常对象,当test方法返回true时,catchError方法的默认参数被调用,当test方法返回false时,catchError方法的默认参数不会被调用。
catchError方法的返回值为Future,可以很方便的进行链式调用。
代码示例:
Future(() {
throw StateError('This is a Dart exception in Future.');
}).catchError((dynamic e, StackTrace stack) => print(e), test: (e) => true);
控制台输出如下
Bad state: This is a Dart exception in Future.
wait
wait用来等待多个异步结果处理完成
代码示例:
Future.wait([
Future.delayed(Duration(seconds: 1), () {
print("1");
}),
Future.delayed(Duration(seconds: 2), () {
print("2");
}),
]).then((value) {
print(3);
});
运行结果:在1S,2S,3S,会分别打印1,2,3
whenComplete
whenComplete用来注册一个在future完成时被调用的函数.
whenComplete的参数是一个没有返回值,没有参数的函数。当future完成时,函数会被调用,不管它是带着一个值还是一个错误。它类似于是一个异步的finally块。
whenComplete和then的区别为,当出现错误时then默认参数中的代码不会被调用,而whenComplete无论何时都会被调用。
whenComplete方法的返回值为Future,可以很方便的进行链式调用。
代码示例:
Future(() {
}).whenComplete(() => print(0));
timeout
timeout在Future延迟指定指定时间后被调用。
timeout的第一个参数Duration timeLimit, 时间间隔。
第二个参数FutureOr<T> Function() onTimeout。
当future执行的时间超过Duration时,函数onTimeout就会被执行,值得注意的是onTimeout的返回值必须和调用它的Future返回值一致,onTimeout函数的返回值如果不是Future,会自动创建一个Future<T>类型。
timeout方法的返回值为Future,可以很方便的进行链式调用。
代码示例:
Future<bool>(() {
return true;
}).timeout(Duration(milliseconds: 2), onTimeout: () {
return false;
})
Flutter中的FutureBuilder
FutureBuilder组件可以监听Future函数的状态 ,根据Future函数的状态来返回不同的组件。
Future的状态
在Dart中异步有三种状态:
- 未完成:第一种状态为未完成,异步方法正在执行当中,还没有返回结果,它的状态就为未完成。
- 已完成并返回一个值:第二种状态为已完成并返回一个值,异步方法执行完成,并返回一个值。
- 已完成并返回一个异常:第三种状态为已完成并返回一个异常,异步方法执行完成,并返回一个异常。
FutureBuilder组件的参数
FutureBuilder有三个主要参数:
- future:
future参数为FutureBuilder组件监听的异步函数。 - initialData:当
FutureBuilder的future参数为空时,initialData的值会赋给snapshot.data。 - builder:
builder参数为小部件构建函数,该函数有两个参数,context和snapshot。
builder函数的参数
builder函数有两个参数context和snapshot:
-
context:上下文对象。
-
snapshot:我们重点来讲解一下
snapshot参数,snapshot参数代表了异步的三种状态1.当异步方法已完成并返回一个值
snapshot.hasData值为true2.当异步方法已完成并返回一个异常
snapshot.hasError值为true3.当异步方法只在执行当中处于未完成的状态时,
snapshot.connectionState为ConnectionState.waiting4.当异步方法已完成
snapshot.connectionState为ConnectionState.done5.当
FutureBuilder的Future参数为空时snapshot.connectionState为ConnectionState.none
FutureBuilder组件的例子
当FutureBuilder监听的Future函数状态发生变化时,FutureBuilder会自动调用builder函数,组件重绘。我们可以根据snapshot参数来判断异步函数当前的状态,根据不同的状态返回不同的组件。
/// Copyright (C), 2020-2021, flutter_demo
/// FileName: futureBuilder_demo
/// Author: Jack
/// Date: 2021/2/4
/// Description:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class FutureBuilderDemo extends StatefulWidget {
@override
_FutureBuilderDemoState createState() => _FutureBuilderDemoState();
}
class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
Future<bool> fetchData() => Future.delayed(Duration(seconds: 1), () {
debugPrint('Step 2, fetch data');
return true;
});
Future fetchData2() => Future.delayed(Duration(seconds: 1), () {
throw Exception();
return Error();
});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: fetchData() ,
initialData: false,
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.waiting){
return Scaffold(
body: Center(
child: Container(child: Text('waiting: ${snapshot.data}'))),
);
}
if(snapshot.connectionState == ConnectionState.none){
return Scaffold(
body: Center(
child: Container(child: Text('none: ${snapshot.data}'))),
);
}
if(snapshot.connectionState == ConnectionState.done){
return Scaffold(
body: Center(
child: Container(child: Text('done: ${snapshot.data}'))),
);
}
if(snapshot.hasError){
return Scaffold(
body: Center(
child: Container(child: Text('hasError: ${snapshot.data}'))),
);
}
if (snapshot.hasData) {
debugPrint('Step 3, build widget: ${snapshot.data}');
// Build the widget with data.
return Scaffold(
body: Center(
child: Container(child: Text('hasData: ${snapshot.data}'))),
);
} else {
// We can show the loading view until the data comes back.
debugPrint('Step 1, build loading widget');
return CircularProgressIndicator();
}
},
);
}
}
优化FutureBuilder的使用
每次重新构建FutureBuilder的父级时,异步任务都将重新启动,很多时候异步任务我们只需要在启动的时候调用一次就可以了。
优化思路
使用变量创建Future,不使用异步方法,异步方法会多次调用,异步变量只会在变量初始化时调用一次。
优化后的代码
/// Copyright (C), 2020-2021, flutter_demo
/// FileName: futureBuilder_demo
/// Author: Jack
/// Date: 2021/2/4
/// Description:
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class FutureBuilderDemo extends StatefulWidget {
@override
_FutureBuilderDemoState createState() => _FutureBuilderDemoState();
}
class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
Future<bool> _data;
@override
void initState() {
super.initState();
_data = fetchData();
}
Future<bool> fetchData() => Future.delayed(Duration(seconds: 1), () {
debugPrint('Step 2, fetch data');
return true;
});
Future fetchData2() => Future.delayed(Duration(seconds: 1), () {
throw Exception();
return Error();
});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _data ,
// future:fetchData(),
initialData: false,
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.waiting){
return Scaffold(
body: Center(
child: Container(child: Text('waiting: ${snapshot.data}'))),
);
}
if(snapshot.connectionState == ConnectionState.none){
return Scaffold(
body: Center(
child: Container(child: Text('none: ${snapshot.data}'))),
);
}
if(snapshot.connectionState == ConnectionState.done){
return Scaffold(
body: Center(
child: Container(child: Text('done: ${snapshot.data}'))),
);
}
if(snapshot.hasError){
return Scaffold(
body: Center(
child: Container(child: Text('hasError: ${snapshot.data}'))),
);
}
if (snapshot.hasData) {
debugPrint('Step 3, build widget: ${snapshot.data}');
// Build the widget with data.
return Scaffold(
body: Center(
child: Container(child: Text('hasData: ${snapshot.data}'))),
);
} else {
// We can show the loading view until the data comes back.
debugPrint('Step 1, build loading widget');
return CircularProgressIndicator();
}
},
);
}
}