获取异步函数的 NULL 值(使用 await 之后)然后更新为新值

Posted

技术标签:

【中文标题】获取异步函数的 NULL 值(使用 await 之后)然后更新为新值【英文标题】:Getting NULL value for async function (after using await) then updating to the new value 【发布时间】:2019-12-24 08:42:46 【问题描述】:

当我运行我的应用程序时,它会引发很多错误,并且我的设备上的红色/黄色错误屏幕会自动刷新并显示预期的输出。

从日志中我可以看到,首先我的对象返回为 null,稍后会以某种方式更新并得到输出。

我最近开始使用 android Dev (Flutter)

我尝试遵循一些在线指南并阅读有关异步响应的相关问题,但没有任何故障排除对我有帮助。 我最大的问题是我无法弄清楚到底是什么问题。

在我的 _AppState 类中:

  void initState() 
    super.initState();
    fetchData();
  

  fetchData() async 
    var cityUrl = "http://ip-api.com/json/";
    var cityRes = await http.get(cityUrl);
    var cityDecodedJson = jsonDecode(cityRes.body);
    weatherCity = WeatherCity.fromJson(cityDecodedJson);
    print(weatherCity.city);
    var weatherUrl = "https://api.openweathermap.org/data/2.5/weather?q=" +
        weatherCity.city +
        "," +
        weatherCity.countryCode +
        "&appid=" +
        //Calling open weather map's API key from apikey.dart
        weatherKey;
    var res = await http.get(weatherUrl);
    var decodedJson = jsonDecode(res.body);
    weatherData = WeatherData.fromJson(decodedJson);
    print(weatherData.weather[0].main);
    setState(() );
  

预期输出(终端):

Mumbai
Rain

实际输出(终端):https://gist.github.com/Purukitto/99ffe63666471e2bf1705cb357c2ea32(实际错误超出了 *** 的正文限制)

截图:

【问题讨论】:

能否请您打印 decodedJson 的内容和 weatherData 的内容以确保数据存在? 问题很简单...fetchData 函数是异步的,因此它不会阻止小部件的构建。当它首先构建时,weatherData 为空。但是一段时间后,API 调用给出了一些响应,然后您调用 setState,这一次它起作用了,因为现在 weatherData 不为空。 【参考方案1】:

asyncawait 是在 Dart 中处理异步编程的机制。

异步操作让您的程序在等待时完成工作 以完成另一个操作。

因此,每当一个方法被标记为async 时,您的程序不会因为该方法的完成而暂停,而只是假设它会在将来的某个时间完成。

示例:错误使用异步函数

以下示例显示了使用异步函数getUserOrder()的错误方法。

String createOrderMessage () 
  var order = getUserOrder(); 
  return 'Your order is: $order';


Future<String> getUserOrder() 
  // Imagine that this function is more complex and slow
  return Future.delayed(Duration(seconds: 4), () => 'Large Latte'); 


main () 
  print(createOrderMessage());

如果你运行上面的程序,它会产生下面的输出 -

Your order is: Instance of '_Future<String>'

这是因为,由于方法的返回类型被标记为Future,程序会将is视为异步方法。

要获得用户的订单,createOrderMessage() 应该调用getUserOrder() 并等待它完成。因为createOrderMessage() 不等待getUserOrder() 完成,所以createOrderMessage() 无法获得getUserOrder() 最终提供的字符串值。


异步和等待

async 和 await 关键字提供了一种声明方式来定义异步函数并使用它们的结果。

因此,无论何时您将函数声明为async,您都可以在任何方法调用之前使用关键字await,这将强制程序在方法完成之前无法继续进行。


例子

在您的情况下,fetchData() 函数被标记为 async 并且您正在使用 await 等待网络调用完成。

但这里fetchData() 的返回类型为Future&lt;void&gt;,因此当您调用initState() 内部的方法时,您必须在不使用async/ await 的情况下这样做,因为initState() 不能标记为async

因此程序不会等待整个fetchData() 方法完成,而是尝试显示本质上是null 的数据。 而且由于在fetchData()内部加载数据后调用setState(),屏幕会刷新,过一段时间可以看到详细信息。

因此出现红黄屏错误。


解决方案

解决此问题的方法是,您可以在屏幕上显示加载指示器,直到数据完全加载。

您可以使用 bool 变量并根据该变量的值更改 UI。

示例 -

class _MyHomePageState extends State<MyHomePage> 
  bool isLoading = false;

  void initState() 
    super.initState();
    fetchData();
 

 fetchData() async 
   setState(() 
     isLoading = true; //Data is loading
   );
   var cityUrl = "http://ip-api.com/json/";
   var cityRes = await http.get(cityUrl);
   var cityDecodedJson = jsonDecode(cityRes.body);
   weatherCity = WeatherCity.fromJson(cityDecodedJson);
   print(weatherCity.city);
   var weatherUrl = "https://api.openweathermap.org/data/2.5/weather?q=" + weatherCity.city + "," +
    weatherCity.countryCode +
    "&appid=" +
    //Calling open weather map's API key from apikey.dart
    weatherKey;
    var res = await http.get(weatherUrl);
    var decodedJson = jsonDecode(res.body);
    weatherData = WeatherData.fromJson(decodedJson);
    print(weatherData.weather[0].main);
    setState(() 
      isLoading = false; //Data has loaded
    );
  

  @override
  Widget build(BuildContext context) 
    return Scaffold(
      body: isLoading ? Center(child : CircularProgressIndicator())
      : Container(), //Replace this line with your actual UI code
    );
  

希望这会有所帮助!

【讨论】:

但我选择在返回 Scaffold 而不是 body 时放置条件,因为我也在 AppBar 中使用该值【参考方案2】:

您必须等到来自 api 的数据到达。快速解决方法是在保存未来数据的变量上放置一个空合并运算符,如下所示:

String text;

fetchData() async 
//...
  text = weatherData.weather[0].main ?? 'Waiting api response...';
//...


// in your build method
@override
Widget build(BuildContext context) 
  return Scaffold(
    body: Container(
      child: Text(text), //this will render "Waiting api response" first, and when the api result arrive, it will change
    ),
  );


否则,您可以使用futureBuilder 小部件来实现此目的。但是你必须将每个api放到不同的函数中,并将其更改为Future,因此它有一个返回值。

Future fetchDataCity() async 
  // your code
  weatherCity = WeatherCity.fromJson(cityDecodedJson);
  return weatherCity;


Future fetchDataWeather() async 
  // your code
  weatherData = WeatherData.fromJson(decodedJson);
  return weatherData;


// in your build method
@override
Widget build(BuildContext context) 
  return Scaffold(
    body: Container(
      child: FutureBuilder(
        future: fetchDataWeather(), // a previously-obtained Future or null
        builder: (BuildContext context, AsyncSnapshot<String> snapshot) 
          switch (snapshot.connectionState)
            case ConnectionState.active:
            case ConnectionState.waiting:
              return Text('Awaiting result...'); //or a placeholder
            case ConnectionState.done:
              if (snapshot.hasError)
                return Text('Error: $snapshot.error');
               else 
                return Text('Error: $snapshot.data');
            
         ,
      ) //FutureBuilder
    ),
  );

【讨论】:

感谢您的洞察力,虽然这似乎可行,但我发现@siddharth-patankar 的答案更容易理解

以上是关于获取异步函数的 NULL 值(使用 await 之后)然后更新为新值的主要内容,如果未能解决你的问题,请参考以下文章

ES6之async的常用简单总结

无法从异步函数获取返回值,等待未按预期工作(Vue API 服务)

用 async/await 来处理异步

Swift之深入解析异步函数async/await的使用与运行机制

异步编程之 async 和 await

从 Nodejs 中的 mySQL 获取数据时如何从 Async/await 函数获取返回值