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

What is a Future and how do I use it?

我收到以下错误:

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'

所以...

如果您熟悉 Task<T> or Promise<T>async/ await 模式,那么您可以直接跳到“如何使用 Future with Flutter 中的小部件”部分。

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

嗯,documentation 说:

An object representing a delayed computation.

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

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

这是通过称为 and . It's not specific to or , it exists under the same name in many other languages. You can find the documentation for Dart here 的模式实现的。

由于需要一些时间的方法无法立即 return,因此它将 return 承诺在完成后交付价值。

那叫一个Future。因此,从数据库中加载一个数字的承诺将 return Future<int> 而 return 来自互联网搜索的电影列表的承诺可能 return Future<List<Movie>> ]. Future<T> 将来 会给你一个 T.

让我们尝试不同的解释:

A future represents the result of an asynchronous operation, and can have two states: uncompleted or completed.

很有可能,因为您不是为了好玩而这样做,您实际上需要 Future<T> 的结果来推进您的应用程序。您需要显示数据库中的编号或找到的电影列表。所以你想等,直到结果出来。这是 await 的用武之地:

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

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

但是等等,我们还没有回到原点吗?不是又要等结果了吗?是的,我们确实是。如果程序没有某种类似于顺序流的东西,它们将是完全混乱的。但重点是我们使用关键字 await 告诉编译器,此时,虽然我们想要等待结果,但我们不希望我们的应用程序只是冻结。我们希望所有其他 运行ning 操作(例如动画)继续。

但是,您只能在本身标记为 async 和 return 和 Future<T> 的函数中使用关键字 await。因为当你 await 某些东西时,正在等待的函数不能再立即 return 它们的结果。你只能return你有什么,如果你必须等待,你必须return承诺稍后交付。

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

    var pizzaBox = await delivery;

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

我们的 getPizza 函数必须 等待 披萨,所以不是 return 立即 Pizza,它必须 return保证将来 会有披萨 。现在您可以反过来 await 某处的 getPizza 函数。

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

flutter 中的所有小部件都需要真实值。不是对稍后出现的价值的承诺。当按钮需要文本时,它不能使用文本稍后出现的承诺。它需要显示按钮 now,因此它需要文本 now.

但有时,您拥有的只是 Future<T>。这就是 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 来显示您的未来结果的方式。

Future<T> 返回将由 async work

完成的潜在价值

例如:

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

上面的代码返回 Future.value(5)int 类型,但是从方法接收值时我们不能使用类型 Future<int>

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. 

下面是来自其他语言的 Dart Future 的类比列表:

  • JS: Promise
  • Java: Future
  • Python: Future
  • C#: Task

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

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

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

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

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

再看看这个Async方法,这里getCarByIdFromApi()方法需要一个id,它是从getCarIdFromDatabase()计算出来的,所以必须有一个await 关键字在第一个方法之前使 Operation 等到 id 被计算并传递给第二个方法。所以这里两个方法一个接一个地在一个Thread.

中处理
Future<Car> getCar() async{
   int id = await getCarIdFromDatabase();
   final Car car = await getCarByIdFromApi(id);
   return car;
}

一个简单的答案是,如果一个函数returns它的值与某个时间的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 获取返回值