Dart中的Future/async/await

RBa*_*iak 18 asynchronous async-await dart flutter

我有一个loadData从文件加载一些文本的函数:

Future<String> loadAsset() async {
  return await rootBundle.loadString('assets/data/entities.json');
}
Run Code Online (Sandbox Code Playgroud)

loadString方法来自Flutter SDK,并且是异步的.

loadAsset随后的方法被调用的另一种方法,必须我标记为async,因为loadAsset是异步,我需要使用await:

Future<List<Entity>> loadEntities() async {
  String jsonData = await loadAsset();
  return parseData(jsonData);
}
Run Code Online (Sandbox Code Playgroud)

parseData方法不是异步的,它接收String,解析它,并返回一个对象列表:

List<Entity> parseData(String jsonString) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

但由于loadEntities必须标记async,这需要它返回一个Future,但实际上,它不是一个Future因为我使用await,它等待loadAsset方法完成,然后parseData使用结果调用函数.

这很容易变成一个async叫雪球,因为每个使用的方法也loadEntities必须标记为async.

另外,我不能loadEntities在类构造函数中使用,因为构造函数应该标记为async,这在Dart中是不允许的.

async/await在Dart中使用的模式错了吗?我怎么能loadEntities在类构造函数中使用该方法?

Gün*_*uer 14

不,异步具有传染性,无法从异步返回同步执行.

async/ await只是语法糖methodThatReturnsFuture().then(...)

标记方法async只是为了允许您await在其体内使用.没有async你仍然需要返回一个Futurefor调用代码,只有在结果loadAsset()可用后执行.

  • 我投了反对票以突出显示其他答案。而不仅仅是像您的消息中那样“您不能这样做”。我理解您的担忧,但您不能对 dart 使用的每一个目的负责。如果人们问如何做到这一点,他们就需要它。 (3认同)

rmt*_*zie 9

您可以直接使用从异步调用返回的Future.这看起来像这样:

class HasAsync {
  HasAsync() {
    asyncFunction().then((val) {
      print(val);
    });
  }

  Future<int> asyncFunction() async {
     int val = await otherFunction();
     return val;
  }
}
Run Code Online (Sandbox Code Playgroud)

你不能在非异步函数中使用await.

正如你用"颤动"标记的那样,我猜这是在一个颤动的应用程序中.如果是这种情况,请查看FutureBuilder文档 - 它可能有助于您尝试做的事情.


Pis*_*usa 6

我知道我可能为时已晚,无法使用此答案,但无论如何我都在写它,希望有人会发现它有用。所以这是我的两分钱。

当我第一次试图弄清楚什么是异步编程以及如何使用它时,我的思考过程与您相同。

由于问题是关于 Flutter,我将使用 dart 来解释这一点。

首先,让我们深入了解在异步编程中使用 async await 的基本目的。

根据 flutter 文档, async 和 await 关键字的目的是声明性地将函数标记为异步并使用它的结果。

  • 要定义异步函数,请在函数体之前添加 async
  • await 关键字仅适用于异步函数。

因此,每当您尝试从标记为异步的函数中获取输出时,它将别无选择,只能返回 Future。查看以下示例以获得更多说明。

  • 首先,你有一个函数可以做一些计算
  • 其次,您有一个简单的函数,它通过执行简单的 http get 请求从 API 获取数据。
  • 最后另一个函数将处理一些数据并打印一些值。

    void processInfo(){
      calculateStuff();
      Future<Map> decodedData = getDataFromInternet();
      getDataProcessed(decodedData);
    }
    
    Run Code Online (Sandbox Code Playgroud)

所以在同步编程中,这意味着所有三个函数将一个接一个地执行。但是假设第二个函数 getDataFromInternet() 是异步调用的。一个简单的实现如下所示。

Future<Map> getDataFromInternet() async {
 http.Response response = await http.get(this._apiPath);

 Map decodedData;

 if (response.statusCode != 200)
   print("invalid response. cannot proceed!");
 else {
   decodedData = jsonDecode(response.body);
 }

 return decodedData;
}
Run Code Online (Sandbox Code Playgroud)

所以上面的函数需要返回一个未来。问题是为什么?这很简单。在这种情况下,这是因为我们想要返回一些东西,并且当 return 语句被执行时,来自“获取请求”的数据可能在那个时刻可用,也可能不可用。

因此,该函数返回一个 Future 类型的结果,该结果要么处于完整状态,要么处于不完整状态。

那么我们如何处理这个结果呢?事实上,这可以通过 3 种方式完成。

1. 方法一——把它当作承诺来处理

因此,一旦 getDataFromInternet() 函数在此示例中返回 Future 结果,您就需要处理该未来结果,就像在 javascript 中处理承诺一样。请参阅下面的代码示例。

void getDataProcessed(Future<Map> data) {
 print('getting data from future map');
 data.then((d) {
   print(d);
 });
}
Run Code Online (Sandbox Code Playgroud)

2.方法二——标记父函数异步(传染方式)

    void processInfo() async{
      calculateStuff();
      //now you can simply await that result and process the result rather
      //than handling a Future<Map> result in this case.
      //Note: it is not required to use future variable because we are awaiting 
      //for result
      Map decodedData = await getDataFromInternet();
      getDataProcessed(decodedData);
    }
Run Code Online (Sandbox Code Playgroud)

所以在这种情况下 getDataProcessed() 函数看起来像这样。

void getDataProcessed(Map data) {
 //this will simply print the data object which is complete result which is by 
 //no way is a promise object
 print(data);
}
Run Code Online (Sandbox Code Playgroud)

3.方法三——在同步函数中使用异步方法的结果(非传染方式)

在这种情况下, processInfo() 函数将略有变化,即 getDataProcessed() 将不再在此方法中被调用,并且看起来像这样。

    void processInfo(){
      calculateStuff();
      getDataFromInternet();
    }
Run Code Online (Sandbox Code Playgroud)

我们可以使用 getDataFromInternet() 函数的结果来调用 getDataProcessed() 函数,而不是在 processInfo() 函数中调用 getDataProcessed()。这意味着我们不必将 processInfo() 标记为异步,我们可以处理 getDataProcessed( ) 方法在我们执行完 getDataFromInternet() 方法之后。以下代码示例演示了如何执行此操作。

void getDataFromInternet() async {
 http.Response response = await http.get(this._apiPath);

 Map decodedData;

 if (response.statusCode != 200)
   print("invalid response. cannot proceed!");
 else {
   decodedData = jsonDecode(response.body);
 }

 //in this case, since we are awaiting for get results response and the 
 //function is not expected to return anything the data type passed into      
 //getDataProcessed() function now will be of type Map rather than being type      
 //Future<Map> . Thus allowing it to be a synchronous function and without 
 //having to handle the future objects.
 getDataProcessed(decodedData);
}


void getDataProcessed(Map data) {
 //this will simply print the data object which is complete result which is by 
 //no way is a promise object
 print(data);
}
Run Code Online (Sandbox Code Playgroud)

所以修改回这个长答案,

  • async/await 只是标记异步函数的声明方式
  • 当一个异步函数被调用时,它可以用 3 种方式处理。
    1. 获取返回 Future 并像使用“then()”函数的承诺一样处理它,因此无需将父函数标记为异步
    2. 将父函数标记为 async 并使用 await 处理返回的对象以强制函数等待结果。
    3. 在异步函数的末尾使用异步函数的输出调用所需的函数。这将允许主函数在等待异步函数的结果时继续非依赖函数,并且一个异步函数获得结果,它可以在最后进入另一个函数并使用接收到的数据执行它。