Dart,如何在自己的函数中创建一个返回future的方法?

95

在Dart中,是否可以创建自己的Future来从方法中返回?或者你必须始终从Dart异步库中的内置Future返回一个?

我想定义一个函数,它始终返回Future<List<Base>>,无论实际上是执行异步调用(文件读取/ajax等)还是仅仅获取本地变量,如下所示:

List<Base> aListOfItems = ...;

Future<List<Base>> GetItemList(){

    return new Future(aListOfItems);

}
6个回答

153

如果你需要创建一个 Future,可以使用 Completer。请参阅文档中的 Completer。以下是示例:

Future<List<Base>> GetItemList(){
  var completer = new Completer<List<Base>>();
    
  // At some time you need to complete the future:
  completer.complete(new List<Base>());
    
  return completer.future;
}

但是大多数情况下,您不需要使用completer来创建future。就像在这种情况下:

Future<List<Base>> GetItemList(){
  var completer = new Completer();
    
  aFuture.then((a) {
    // At some time you need to complete the future:
    completer.complete(a);
  });
    
  return completer.future;
}

使用完整器后,代码可能会变得非常复杂。而实际上,您可以直接使用以下方式,因为then()也返回一个Future
Future<List<Base>> GetItemList(){
  return aFuture.then((a) {
    // Do something..
  });
}

还有一个有关文件输入输出的示例:

Future<List<String>> readCommaSeperatedList(file){
  return file.readAsString().then((text) => text.split(','));
}

查看这篇博客文章获取更多技巧。


83

您可以简单地使用Future<T>value工厂构造函数:

return Future<String>.value('Back to the future!');

可能我做错了什么,但我得到 .value('回到未来!') 被标记为死代码。 - Steve Gelman
但是在接收回调的位置,我们该如何处理<T>对象,在您的情况下是<String>(回到未来!)? - AAEM
这个解决方案对我来说是最简单的。我使用了以下代码:<br/> return Future<List<MyClass>>.value(new List<MyClass>()); - ezaspi

50

从自己的函数返回一个Future

本文总结了多种实现方法。

起点

你的方法可以是任何形式,但为了举例方便,我们假设你的方法如下:

int cubed(int a) {
  return a * a * a;
}

目前您可以这样使用您的方法:

int myCubedInt = cubed(3);  // 27

然而,您希望您的方法像这样返回一个Future

Future<int> myFutureCubedInt = cubed(3);

或者更实际地像这样使用:

int myCubedInt = await cubed(3);

以下解决方案都展示了如何实现这一点。
解决方案1:Future() 构造函数
最基本的解决方案是使用 Future 的生成构造函数。
Future<int> cubed(int a) {
  return Future(() => a * a * a);
}

我将该方法的返回类型更改为Future<int>,然后将旧函数的工作作为匿名函数传递给Future构造函数。
解决方案2:Future命名构造函数
Futures可以完成值或错误。因此,如果您想明确指定这两个选项中的任何一个,可以使用Future.valueFuture.error命名构造函数。
Future<int> cubed(int a) {
  if (a < 0) {
    return Future.error(ArgumentError("'a' must be positive."));
  }
  return Future.value(a * a * a);
}

不允许a的负值只是一个刻意的例子,用来展示Future.error构造函数的使用。如果没有任何可能引起错误的情况,那么可以简单地使用Future.value构造函数,如下所示:

Future<int> cubed(int a) {
  return Future.value(a * a * a);
}

解决方案3:异步方法

async方法会自动返回Future,因此您只需将方法标记为async并更改返回类型即可:

Future<int> cubed(int a) async {
  return a * a * a;
}

通常情况下,您会将 asyncawait 结合使用,但并不是必须的。Dart 会自动将返回值转换为 Future。如果您在函数体内使用另一个返回 Future 的 API,可以像这样使用 await
Future<int> cubed(int a) async {
  return await cubedOnRemoteServer(a);
}

或者使用Future.then语法来表达相同的意思:

Future<int> cubed(int a) async {
  return cubedOnRemoteServer(a).then((result) => result);
}

解决方案4:Completer

使用Completer是最低级别的解决方案。只有在上述解决方案无法覆盖某些复杂逻辑时才需要使用。

import 'dart:async';

Future<int> cubed(int a) async {
  final completer = Completer();
  if (a < 0) {
    completer.completeError(ArgumentError("'a' must be positive."));
  } else {
    completer.complete(a * a * a);
  }
  return completer.future;
}

这个例子与上面的命名构造函数解决方案类似。它处理加法中的错误,并以正常方式完成future。

关于阻塞UI的说明

使用future并不能保证不会阻塞用户界面(UI)(也就是主隔离区)。从函数返回future只是告诉Dart将任务安排在事件队列的末尾。如果该任务十分密集,则当事件循环调度运行它时,仍然会阻塞UI。

如果您有一个密集型任务需要在另一个隔离区运行,则必须在其上启动一个新的隔离区运行它。当该任务在其他隔离区上完成时,它将作为future返回消息,您可以将其作为函数的结果传递。

许多标准Dart IO类(例如FileHttpClient)具有委托工作给系统的方法,因此不会在您的UI线程上执行密集的工作。因此,这些方法返回的futures是安全的,不会阻塞您的UI。

另请参见


2
这是一个绝妙的答案!我真的很感激关于为什么在重负载情况下await仍然会阻塞UI的说明。 - Tomasz Cz.

7

@Fox32提供了正确的答案,除此之外,我们还需要提到Completer的类型,否则会出现异常。

收到的异常类型为 'Future<dynamic>' 不是类型为 'FutureOr<List<Base>>' 的子类型

因此,completer的初始化应该变成:

var completer = new Completer<List<Base>>();


但是当控制从该函数返回时,我们如何处理(接收)对象“List<Base>”? - AAEM

1

虽然不是针对所提出的问题的答案,但有时我们可能希望等待一个闭包:

    flagImage ??= await () async {
      ...
      final image = (await codec.getNextFrame()).image;
      return image;
    }();

我认为即使我们没有将其传递到任何地方,它也会隐含地创造一个未来。

0
这是一个简单的条件Future示例。
String? _data;
Future<String> load() async {
    // use preloaded data
    if (_data != null) return Future<String>.value(_data);
    // load data only once
    String data = await rootBundle.loadString('path_to_file');
    _data = data;
    return data;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接