【问题标题】:async is snowballing to callers, can't make constructor async异步对调用者滚雪球,不能使构造函数异步
【发布时间】:2018-09-07 08:16:27
【问题描述】:

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

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

loadString 方法来自 Flutter SDK,是异步的。

然后在另一个方法中调用loadAsset 方法,我必须将其标记为async,因为loadAsset 是异步的,我需要使用await

Future<List<Entity>> loadEntities() async {
  String jsonData = await loadAsset();
  return parseData(jsonData);
}

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

List<Entity> parseData(String jsonString) {
  ...
}

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

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

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

我在 Dart 中使用 async/await 模式错了吗?如何在类构造函数中使用loadEntities 方法?

【问题讨论】:

    标签: asynchronous async-await dart flutter


    【解决方案1】:

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

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

    async 标记一个方法只是为了让你在它的内部使用await。如果没有async,您仍然需要返回一个Future,以便调用代码仅在loadAsset() 的结果可用后执行。

    【讨论】:

    • 这是解释,但不是解决方案。有一种方法可以同步事件并正常执行。
    • 浏览器中没有办法。在服务器端和 Flutter 有一种方法,您应该不惜一切代价避免。所以正确的答案是“没有办法”,即使你理论上可以说有。如果您还没有准备好接受这一点,我强烈建议不要使用 Dart、JS、TypeScript 或任何类型的语言,并寻找其他单事件队列驱动的语言。感谢您对 BTW 的反对
    • 我不赞成突出其他答案。而不仅仅是“你不能那样做”,就像你的消息中那样。我理解您的担忧,但您不能对飞镖使用的所有目的负责。如果人们问如何做到这一点,他们需要它。
    • 顺便说一句,你可以采取更容易。你可以忍受downvote。并且不要建议人们使用什么语言。
    【解决方案2】:

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

    class HasAsync {
      HasAsync() {
        asyncFunction().then((val) {
          print(val);
        });
      }
    
      Future<int> asyncFunction() async {
         int val = await otherFunction();
         return val;
      }
    }
    

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

    由于您已将其标记为“颤振”,我猜这是在颤振应用程序中。如果是这种情况,请查看docs for FutureBuilder - 它可能有助于您尝试做的事情。

    【讨论】:

    • 谢谢,这是一个 Flutter 应用,我会仔细看看 FutureBuilder。
    【解决方案3】:

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

    当我第一次尝试弄清楚什么是异步编程以及如何使用它时,我的思考过程和你一样。

    由于问题是关于 Flutter 的,我就用 dart 来解释一下。

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

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

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

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

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

      void processInfo(){
        calculateStuff();
        Future<Map> decodedData = getDataFromInternet();
        getDataProcessed(decodedData);
      }
      

    所以在同步编程中,这意味着所有三个函数将一个接一个地执行。但是假设第二个函数 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;
    }
    

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

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

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

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

    因此,一旦 getDataFromInternet() 函数在此示例中返回 Future 结果,您就需要该 Future 结果的过程,就像您在 javascript 中处理 promise 的方式一样。请参考下面的代码示例。

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

    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);
        }
    

    所以在这种情况下,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);
    }
    

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

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

        void processInfo(){
          calculateStuff();
          getDataFromInternet();
        }
    

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

    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);
    }
    

    所以修改这个长答案,

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

    【讨论】:

      【解决方案4】:

      最简单的构造函数是Future(),它接受一个函数并返回一个与函数返回类型匹配的future。稍后函数异步运行,future 以函数的返回值完成。下面是一个使用Future()的例子:

      void main() {
        final myFuture = Future(() {
          print('Creating the future.'); // Prints second.
          return 12;
        });
        print('Done with main().'); // Prints first.
      }
      

      要使用完成的值,您可以使用then()。这是每个未来的实例方法,您可以使用它来注册一个回调,以在未来完成一个值时使用。你给它一个函数,该函数接受一个与未来类型匹配的参数。一旦 future 以一个值结束,您的函数就会使用该值调用。

      void main() {
        Future.delayed(
          const Duration(seconds: 3),
          () => 100,
        ).then((value) {
          print('The value is $value.'); // Prints later, after 3 seconds.
        });
        print('Waiting for a value...'); // Prints first.
      }
      

      欲了解更多信息,请参阅https://medium.com/dartlang/dart-asynchronous-programming-futures-96937f831137

      【讨论】:

        【解决方案5】:

        thenawait 是不同的。 await 将在那里停止程序,直到 Future 任务完成。但是then 不会阻止该程序。 then 中的块会在之后Future 任务完成时执行。

        如果您希望程序等待Future 任务,请使用await。如果您希望您的程序继续运行并且Future 任务在“后台”执行它,请使用then

        至于你的问题,我建议重新设计。在其他地方执行构造函数所需的加载资产和其他async 事情。完成这些任务后,再调用构造函数。

        【讨论】:

          猜你喜欢
          • 2012-08-05
          • 2020-07-28
          • 2018-09-16
          • 1970-01-01
          • 2014-10-28
          • 2018-06-26
          • 1970-01-01
          • 1970-01-01
          • 2014-05-27
          相关资源
          最近更新 更多