理解 Future,在 Flutter 中等待
Understanding Future, await in Flutter
我已经阅读了大部分关于 Flutter 中 Futures 的文档。但我仍然不明白如何从函数中 return 一个 none 未来值。
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp( Kitchen());
class Kitchen extends StatelessWidget {
String caption = '-none-';
Kitchen({Key? key}) : super(key: key);
String retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
String result = response.toString();
return result;
}
@override
Widget build(BuildContext context) {
caption = retrieve();
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') )
, body: Container(
color: Colors.cyan[50]
, child: Center( child: Text(caption) )
)
)
);
}
}
代码不会 运行 因为 retrieve 的 return 类型必须是写入的 Future。那么,如果除了 Future 之外我永远无法从该函数 return 得到任何东西,为什么还要使用 await 关键字呢?为什么 return 类型的 await 语句不是 Future。没看懂。
我喜欢将未来视为“soon-to-be 值”。
Futures 可以(而且应该!)有一个类型参数来解释未来最终会是什么(如果未来永远不会是未来以外的任何东西,你可以使用 void)
Future<String> someLongComputation() async { ... }
以上,someLongComputation
将立即 return 一个未来,一段时间后,所说的未来将用一个字符串解析。
await 关键字的作用是等到 future returned 一个值然后 returns 说值,基本上将异步计算变成同步计算,当然这会否定首先使它成为异步的重点,因此 await 关键字只能在另一个异步函数内部使用。
你不能return一个异步函数的字符串,一个字符串必须立即有一个值,但是一个异步函数永远不会立即有一个值,你应该做的是return未来的字符串:
Future<String> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
String result = response.toString();
return result;
}
但这是一个问题,因为您正试图在小部件上显示字符串。您希望在 运行 函数中看到什么?如果您没有互联网并且该功能卡住了一分钟怎么办?
您有两个选择:
您可以使用 setState 和有状态小部件:
class Kitchen extends StatefulWidget{
...
}
class _KitchenState extends State<Kitchen> {
String caption = '-none-';
Kitchen({Key? key}) : super(key: key);
Future<void> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
String result = response.toString();
setState(() => caption = result);
}
@override
initState() {
super.initState();
retrieve();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') )
, body: Container(
color: Colors.cyan[50]
, child: Center( child: Text(caption) )
)
)
);
}
}
如您所见,我删除了 return 类型并将其更改为对 setState
的调用,我还在 initstate 上调用该函数而不是 build
.
您的第二个选择是使用 FutureBuilder
来完成类似的事情:
class Kitchen extends StatelessWidget {
Kitchen({Key? key}) : super(key: key);
Future<String> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
String result = response.toString();
return result;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: retrieve(),
builder: (context, snapshot) {
final caption = snapshot.data ?? '-none-';
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') ),
body: Container(
color: Colors.cyan[50],
child: Center( child: Text(caption) )
)
)
);
}
);
}
上面的代码可能看起来很丑陋,但您需要了解的是 FutureBuilder
小部件有两个参数:future
和 builder
,future
只是您要使用的未来,而 builder
是一个带有两个参数的函数,而 return 是一个小部件。 FutureBuilder
将运行这个功能在未来前后完成。这两个参数是当前上下文和一个 snapshot
,它有一些有用的东西:
您可以通过 snapshot.data
访问未来的结果,如果未来尚未完成,它将为空!
也可以看看有没有带snapshot.hasData
的数据。
您可以查看 snapshot.hasError
和 snapshot.error
是否有问题。
最后,您可以看到 snapshot.connectionState
的未来状态,它可能有 4 个值之一:
- none,也许有一些初始数据。
- waiting,表示异步操作已经开始,通常数据为null。
- 活跃,数据为 non-null,并且可能随时间变化。
- 完成,数据为 non-null。
检查使用 if (snapshot.connectionState == ConnectionState.done)
例如。
要更好地理解snapshot
和FutureBuilder
,您可以查看文档:
快照(其实叫异步快照):
https://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html
未来建设者:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
这是一个在屏幕中间打印当前时间的工作示例。创建初始小部件时,它将打印 -none- 然后一旦未来完成,它将产生当前日期和时间。这都是基于上面的答案。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp( Kitchen());
class Kitchen extends StatefulWidget{
Kitchen( {Key? key,}) : super(key: key);
@override
_KitchenState createState(){
return _KitchenState();
}
}
class _KitchenState extends State<Kitchen> {
String caption = '-none-';
Future<void> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
Map body = json.decode( response.body );
String result = body['currentDateTime'];
setState(() => caption = result);
}
@override
initState() {
super.initState();
retrieve();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') )
, body: Container(
color: Colors.cyan[50]
, child: Center( child: Text(caption) )
)
)
);
}
}
基于第一个答案的工作 futureBuilder 示例。就像 initState 版本一样工作。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp( Kitchen());
class Kitchen extends StatefulWidget{
Kitchen( {Key? key,}) : super(key: key);
@override
_KitchenState createState(){
return _KitchenState();
}
}
class _KitchenState extends State<Kitchen> {
Future<String> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
Map body = json.decode( response.body );
String result = body['currentDateTime'];
return result;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: retrieve(),
builder: (context, snapshot) {
final caption = snapshot.data ?? '-none-';
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') ),
body: Container(
color: Colors.cyan[50],
child: Center( child: Text( caption.toString() ) )
)
)
);
}
);
}
}
带有进度指示器的工作示例。我猜是一种加载程序的方法。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp( Kitchen());
class Kitchen extends StatefulWidget{
Kitchen( {Key? key,}) : super(key: key);
//===================================================
// createState =
//===================================================
@override
_KitchenState createState(){
return _KitchenState();
}
}
class _KitchenState extends State<Kitchen> {
Future<bool> wasteTime() {
return Future.delayed(const Duration(seconds: 2)).then((onValue) => true);
}
Future<String> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
Map body = json.decode( response.body );
String result = body['currentDateTime'];
// here only to show the CircularProgressIndicator to the user.
// The network calls are fairly quick.
await wasteTime();
return result;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: retrieve(),
builder: (context, snapshot) {
if( snapshot.connectionState == ConnectionState.done
&& snapshot.hasError )
{
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') ),
body: Container(
color: Colors.cyan[50],
child: const Center( child: Text( 'Error message' ) )
)
)
);
}
else if( snapshot.connectionState == ConnectionState.done
&& snapshot.hasData )
{
final caption = snapshot.data ?? '-none-';
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Kitchen async demo')
),
body: Container(
color: Colors.cyan[50],
child: Center(
child: Text( caption.toString() )
)
)
)
);
}
else {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') ),
body: Container(
color: Colors.cyan[50],
child: const Center(
child: CircularProgressIndicator(
value: null, color: Colors.blue
)
)
)
)
);
}
}
);
}
}
我已经阅读了大部分关于 Flutter 中 Futures 的文档。但我仍然不明白如何从函数中 return 一个 none 未来值。
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp( Kitchen());
class Kitchen extends StatelessWidget {
String caption = '-none-';
Kitchen({Key? key}) : super(key: key);
String retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
String result = response.toString();
return result;
}
@override
Widget build(BuildContext context) {
caption = retrieve();
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') )
, body: Container(
color: Colors.cyan[50]
, child: Center( child: Text(caption) )
)
)
);
}
}
代码不会 运行 因为 retrieve 的 return 类型必须是写入的 Future。那么,如果除了 Future 之外我永远无法从该函数 return 得到任何东西,为什么还要使用 await 关键字呢?为什么 return 类型的 await 语句不是 Future。没看懂。
我喜欢将未来视为“soon-to-be 值”。
Futures 可以(而且应该!)有一个类型参数来解释未来最终会是什么(如果未来永远不会是未来以外的任何东西,你可以使用 void)
Future<String> someLongComputation() async { ... }
以上,someLongComputation
将立即 return 一个未来,一段时间后,所说的未来将用一个字符串解析。
await 关键字的作用是等到 future returned 一个值然后 returns 说值,基本上将异步计算变成同步计算,当然这会否定首先使它成为异步的重点,因此 await 关键字只能在另一个异步函数内部使用。
你不能return一个异步函数的字符串,一个字符串必须立即有一个值,但是一个异步函数永远不会立即有一个值,你应该做的是return未来的字符串:
Future<String> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
String result = response.toString();
return result;
}
但这是一个问题,因为您正试图在小部件上显示字符串。您希望在 运行 函数中看到什么?如果您没有互联网并且该功能卡住了一分钟怎么办?
您有两个选择:
您可以使用 setState 和有状态小部件:
class Kitchen extends StatefulWidget{
...
}
class _KitchenState extends State<Kitchen> {
String caption = '-none-';
Kitchen({Key? key}) : super(key: key);
Future<void> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
String result = response.toString();
setState(() => caption = result);
}
@override
initState() {
super.initState();
retrieve();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') )
, body: Container(
color: Colors.cyan[50]
, child: Center( child: Text(caption) )
)
)
);
}
}
如您所见,我删除了 return 类型并将其更改为对 setState
的调用,我还在 initstate 上调用该函数而不是 build
.
您的第二个选择是使用 FutureBuilder
来完成类似的事情:
class Kitchen extends StatelessWidget {
Kitchen({Key? key}) : super(key: key);
Future<String> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
String result = response.toString();
return result;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: retrieve(),
builder: (context, snapshot) {
final caption = snapshot.data ?? '-none-';
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') ),
body: Container(
color: Colors.cyan[50],
child: Center( child: Text(caption) )
)
)
);
}
);
}
上面的代码可能看起来很丑陋,但您需要了解的是 FutureBuilder
小部件有两个参数:future
和 builder
,future
只是您要使用的未来,而 builder
是一个带有两个参数的函数,而 return 是一个小部件。 FutureBuilder
将运行这个功能在未来前后完成。这两个参数是当前上下文和一个 snapshot
,它有一些有用的东西:
您可以通过 snapshot.data
访问未来的结果,如果未来尚未完成,它将为空!
也可以看看有没有带snapshot.hasData
的数据。
您可以查看 snapshot.hasError
和 snapshot.error
是否有问题。
最后,您可以看到 snapshot.connectionState
的未来状态,它可能有 4 个值之一:
- none,也许有一些初始数据。
- waiting,表示异步操作已经开始,通常数据为null。
- 活跃,数据为 non-null,并且可能随时间变化。
- 完成,数据为 non-null。
检查使用 if (snapshot.connectionState == ConnectionState.done)
例如。
要更好地理解snapshot
和FutureBuilder
,您可以查看文档:
快照(其实叫异步快照): https://api.flutter.dev/flutter/widgets/AsyncSnapshot-class.html
未来建设者: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
这是一个在屏幕中间打印当前时间的工作示例。创建初始小部件时,它将打印 -none- 然后一旦未来完成,它将产生当前日期和时间。这都是基于上面的答案。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp( Kitchen());
class Kitchen extends StatefulWidget{
Kitchen( {Key? key,}) : super(key: key);
@override
_KitchenState createState(){
return _KitchenState();
}
}
class _KitchenState extends State<Kitchen> {
String caption = '-none-';
Future<void> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
Map body = json.decode( response.body );
String result = body['currentDateTime'];
setState(() => caption = result);
}
@override
initState() {
super.initState();
retrieve();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') )
, body: Container(
color: Colors.cyan[50]
, child: Center( child: Text(caption) )
)
)
);
}
}
基于第一个答案的工作 futureBuilder 示例。就像 initState 版本一样工作。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp( Kitchen());
class Kitchen extends StatefulWidget{
Kitchen( {Key? key,}) : super(key: key);
@override
_KitchenState createState(){
return _KitchenState();
}
}
class _KitchenState extends State<Kitchen> {
Future<String> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
Map body = json.decode( response.body );
String result = body['currentDateTime'];
return result;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: retrieve(),
builder: (context, snapshot) {
final caption = snapshot.data ?? '-none-';
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') ),
body: Container(
color: Colors.cyan[50],
child: Center( child: Text( caption.toString() ) )
)
)
);
}
);
}
}
带有进度指示器的工作示例。我猜是一种加载程序的方法。
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp( Kitchen());
class Kitchen extends StatefulWidget{
Kitchen( {Key? key,}) : super(key: key);
//===================================================
// createState =
//===================================================
@override
_KitchenState createState(){
return _KitchenState();
}
}
class _KitchenState extends State<Kitchen> {
Future<bool> wasteTime() {
return Future.delayed(const Duration(seconds: 2)).then((onValue) => true);
}
Future<String> retrieve() async {
const String link = 'http://worldclockapi.com/api/json/utc/now';
Uri url = Uri.parse(link);
http.Response response = await http.get(url);
Map body = json.decode( response.body );
String result = body['currentDateTime'];
// here only to show the CircularProgressIndicator to the user.
// The network calls are fairly quick.
await wasteTime();
return result;
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: retrieve(),
builder: (context, snapshot) {
if( snapshot.connectionState == ConnectionState.done
&& snapshot.hasError )
{
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') ),
body: Container(
color: Colors.cyan[50],
child: const Center( child: Text( 'Error message' ) )
)
)
);
}
else if( snapshot.connectionState == ConnectionState.done
&& snapshot.hasData )
{
final caption = snapshot.data ?? '-none-';
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Kitchen async demo')
),
body: Container(
color: Colors.cyan[50],
child: Center(
child: Text( caption.toString() )
)
)
)
);
}
else {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(title: const Text('Kitchen async demo') ),
body: Container(
color: Colors.cyan[50],
child: const Center(
child: CircularProgressIndicator(
value: null, color: Colors.blue
)
)
)
)
);
}
}
);
}
}