理解 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 小部件有两个参数:futurebuilderfuture 只是您要使用的未来,而 builder 是一个带有两个参数的函数,而 return 是一个小部件。 FutureBuilder将运行这个功能在未来前后完成。这两个参数是当前上下文和一个 snapshot,它有一些有用的东西:

您可以通过 snapshot.data 访问未来的结果,如果未来尚未完成,它将为空!

也可以看看有没有带snapshot.hasData的数据。

您可以查看 snapshot.hasErrorsnapshot.error 是否有问题。

最后,您可以看到 snapshot.connectionState 的未来状态,它可能有 4 个值之一:

  • none,也许有一些初始数据。
  • waiting,表示异步操作已经开始,通常数据为null。
  • 活跃,数据为 non-null,并且可能随时间变化。
  • 完成,数据为 non-null。

检查使用 if (snapshot.connectionState == ConnectionState.done) 例如。

要更好地理解snapshotFutureBuilder,您可以查看文档:

快照(其实叫异步快照): 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
                                    )
                                )
                            )
                        )
                    );
                }
            }
        );
    }
}