什么是 Future 以及如何使用它?

Posted

技术标签:

【中文标题】什么是 Future 以及如何使用它?【英文标题】:What is a Future and how do I use it? 【发布时间】:2022-01-10 10:03:26 【问题描述】:

我收到以下错误:

A value of type 'Future<int>' can't be assigned to a variable of type 'int'

它可能是另一种类型而不是int,但基本上模式是

A value of type 'Future<T>' can't be assigned to a variable of type 'T'

所以...

Future 到底是什么? 如何获得我想要获得的实际价值? 当我只有Future&lt;T&gt; 时,我用什么小部件来显示我的值?

【问题讨论】:

如果您想知道,这应该是该主题的规范答案,因为它似乎以前不存在。请参阅this 元帖子。 如果您觉得有什么可以改进的地方,请随时编辑 Q 和 A。 【参考方案1】:

如果你熟悉 Task&lt;T&gt;Promise&lt;T&gt;async/ await 模式,那么你可以直接跳到“如何使用 Flutter 中的小部件使用 Future”部分.

什么是 Future 以及如何使用它?

好吧,documentation 说:

表示延迟计算的对象。

没错。它也有点抽象和干燥。通常,函数会返回结果。依次。该函数被调用、运行并返回它的结果。在那之前,呼叫者等待。某些功能,尤其是当它们访问硬件或网络等资源时,需要一些时间来完成。想象一下从 Web 服务器加载头像图片,从数据库加载用户数据或从设备内存加载多种语言的应用程序文本。这可能会很慢。

默认情况下,大多数应用程序只有一个控制流。当这个流程被阻塞时,例如通过等待需要时间的计算或资源访问,应用程序就会冻结。如果您足够大,您可能会记住这是标准,但在当今世界,这将被视为错误。即使有些事情需要时间,我们也会得到一点动画。一个微调器,一个沙漏,也许是一个进度条。但是应用程序如何在运行并显示动画的同时仍然等待结果呢?答案是:异步操作。在您的代码等待某事时仍在运行的操作。现在编译器如何知道它是否应该停止一切并等待结果或继续所有后台工作并仅在这种情况下等待?好吧,它自己无法弄清楚。我们必须告诉它。

这是通过称为async 和await 的模式实现的。它并不特定于flutter 或dart,它在许多其他语言中都以相同的名称存在。你可以找到 Dart here 的文档。

由于需要一些时间的方法不能立即返回,所以它会在完成后返回传递值的承诺。

这称为Future。因此,从数据库加载数字的承诺将返回Future&lt;int&gt;,而从互联网搜索返回电影列表的承诺可能返回Future&lt;List&lt;Movie&gt;&gt;Future&lt;T&gt;将来会给你一个T的东西。

让我们尝试不同的解释:

Future 表示异步操作的结果,可以有两种状态:未完成或已完成。

很可能,因为您这样做并不是为了好玩,您实际上需要Future&lt;T&gt; 的结果才能在您的应用程序中取得进展。您需要显示数据库中的数字或找到的电影列表。所以你要等,直到结果出来。这就是await 的用武之地:

Future<List<Movie>> result = loadMoviesFromSearch(input);

// right here, you need the result. So you wait for it:
List<Movie> movies = await result;

但是等等,我们不是绕了一圈吗?我们不是又要等结果了吗?是的,我们确实是。如果程序没有顺序流的相似性,它们将是完全混乱的。但关键是,使用关键字await 我们已经告诉编译器,此时,虽然我们想等待结果,但我们不希望我们的应用程序只是冻结。我们希望所有其他正在运行的操作(例如动画)继续进行。

但是,您只能在本身标记为async 的函数中使用await关键字并返回Future&lt;T&gt;。因为当你await 某事时,正在等待的函数不能再立即返回它们的结果。您只能退回您所拥有的,如果您必须等待它,您必须返回一个承诺稍后交付。

Future<Pizza> getPizza() async 
    Future<PizzaBox> delivery = orderPizza();        

    var pizzaBox = await delivery;

    var pizza = pizzaBox.unwrap();
    
    return pizza;   

我们的 getPizza 函数必须等待披萨,所以不是立即返回Pizza,它必须返回披萨将来会在那里的承诺。现在您可以反过来在某处await getPizza 函数。

如何在 Flutter 中使用 Future 和小部件?

flutter 中的所有小部件都期望真实值。没有一些承诺会在以后出现价值。当按钮需要文本时,它不能使用稍后会出现文本的承诺。它需要显示按钮now,所以它需要文本now

但有时,您所拥有的只是Future&lt;T&gt;。这就是FutureBuilder 的用武之地。您可以在有未来时使用它,在等待它时显示一件事(例如进度指示器),在完成时显示另一件事(例如结果)。

让我们看一下我们的披萨示例。您想订购比萨饼,您在等待时需要一个进度指示器,您想在送达后查看结果,并且可能会在出现错误时显示错误消息:

import 'package:flutter/material.dart';

void main() 
  runApp(MyApp());


/// ordering a pizza takes 5 seconds and then gives you a pizza salami with extra cheese
Future<String> orderPizza() 
  return Future<String>.delayed(const Duration(seconds: 5), () async => 'Pizza Salami, Extra Cheese');


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      theme: ThemeData.dark(),
      home: Scaffold(
        body: Center(
          child: PizzaOrder(),
        ),
      ),
    );
  


class PizzaOrder extends StatefulWidget 
  @override
  _PizzaOrderState createState() => _PizzaOrderState();


class _PizzaOrderState extends State<PizzaOrder> 
  Future<String>? delivery;

  @override
  Widget build(BuildContext context) 
    return Column(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          ElevatedButton(
            onPressed: delivery != null ? null : () => setState(()  delivery = orderPizza(); ),
            child: const Text('Order Pizza Now')
          ),
          delivery == null
            ? const Text('No delivery scheduled')
            : FutureBuilder(
              future: delivery,
              builder: (context, snapshot) 
                if(snapshot.hasData) 
                  return Text('Delivery done: $snapshot.data');
                 else if(snapshot.hasError) 
                  return Text('Delivery error: $snapshot.error.toString()');
                 else 
                  return const CircularProgressIndicator();
                
              )
        ]);
  

这就是你如何使用 FutureBuilder 来显示你未来的结果。

【讨论】:

@dev-aentgs 嵌套.then 将具有与await 完全相同的行为。 await 旨在使代码更清晰易懂。 我认为您将两件事混为一谈:await.then 不可互换。他们不做同样的事情。然而,一个程序可以使用大量嵌套的.then 链(以及至少一个Future.wait)来实现相同的目标,它可以通过 async/await 实现更清晰和更清晰。我不确定我是否会称它为语法糖。它停在哪里?不是 mov/cmp/jmp 语法糖之外的一切吗? 它们当然不能直接互换,但如果.then 被正确实现,Future.wait 就没有必要了。至于它是否真的是“语法糖”,我想这可能是您自己的看法,但似乎一致认为await许多支持它的语言中只是语法糖。 无论如何,我认为在答案中提供此信息并使用.then 显示替代方案可能是个好主意。 await.then 的语法糖。 await 转换为相应的 .then(可能还有 .catchError.whenCompleted)回调。任何可以用await 完成的事情都可以通过在Future 上手动注册适当的回调来完成。至于在哪里划清界限:“语法糖”的典型定义是可以从语言中删除而不删除功能的东西。【参考方案2】:

Future&lt;T&gt; 返回将由async 工作完成的潜在值

例如:

Future<int> getValue() async 
    return Future.value(5);
  

上面的代码返回Future.value(5),它是int类型,但是在从方法接收值时,我们不能使用Future&lt;int&gt;类型,即

Future<int> value = await getValue();  // Not Allowed
// Error
A value of type 'Future<int>' can't be assigned to a variable of type 'int'

解决上面的getValue()应该在int类型下接收

  int value = await getValue(); // right way as it returning the potential value. 

【讨论】:

我仍然不能在小部件构建方法中使用int value = await getValue();,它说“等待表达式只能在异步函数中使用。”因为 Build 方法不是异步初学者问题.. 但我不知道放在哪里 是的,不能直接使用,应该使用FutureBuilder或者新建方法,使用setState()更新数据【参考方案3】:

以下是 Dart 的 Future 与其他语言的类比列表:

JS:Promise Java:Future 蟒蛇:Future C#:Task

就像在其他语言中一样,Future 是一种特殊类型的对象,它允许使用 async/await 语法糖,以同步/线性方式编写异步代码。您从异步方法返回 Future 而不是接受回调作为参数并避免回调地狱 - Futures 和回调都解决相同的问题(稍后触发一些代码)但方式不同。

【讨论】:

【参考方案4】:

我希望这个关键点能提供信息,我用两种不同的异步方法来展示它:

注意以下方法,其中showLoading()getAllCarsFromApi()hideLoading() 是内部Async 方法。

如果我将await 关键字放在showLoading() 之前,操作 会等到它完成然后转到下一行,但我故意删除了await,因为我需要我的正在加载对话框与正在处理getAllCarsFromApi()同时显示,因此这意味着showLoading()getAllCarsFromApi()方法在不同的线程上处理。最后hideLoading() 隐藏了加载对话框。

Future<List<Car>> getData() async
   showLoading();
   final List<Car> cars = await getAllCarsFromApi();
   hideLoading();
   return cars;

现在看这个另一个Async方法,这里getCarByIdFromApi()方法需要一个id,它是从getCarIdFromDatabase()计算出来的,所以在第一个方法之前必须有一个await关键字才能使操作等到id被计算出来并传递给第二种方法。所以这里两个方法一个接一个地在一个Thread中处理。

Future<Car> getCar() async
   int id = await getCarIdFromDatabase();
   final Car car = await getCarByIdFromApi(id);
   return car;

【讨论】:

【参考方案5】:

一个简单的答案是,如果一个函数在某个时间返回其值并带有delay,则使用Future 来获取其值。

Future<int> calculate(required int val1, required int val2) async 
    await Future.delayed(const Duration(seconds: 2));
    return val1 + val2;
  

如果我们把上面的函数称为

getTotal() async  

      int result = calculate(val1: 5, val2: 5);

    print(result);
  

我们会得到以下错误:

A value of type 'Future<int>' can't be assigned to a variable of type 'int'

但是如果我们在函数调用之前使用 await 它将在延迟后给出函数的实际返回值

getTotal() async  

    int result = await calculate(val1: 5, val2: 5);

   print(result);
 

关键字async 需要使用await 来获取Future 的返回值

【讨论】:

以上是关于什么是 Future 以及如何使用它?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter异步编程

如何使用 Flutter 数据模型和 Future Builder

__future__ 进口如何在幕后工作

如何将 Future 作为函数参数传递?

Combine如何驯服Future发布器的“怪癖”

__future__ 模块在 Python 2.7 中如何工作? [复制]