获取异步函数的 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】:
async
和 await
是在 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<void>
,因此当您调用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 之后)然后更新为新值的主要内容,如果未能解决你的问题,请参考以下文章
无法从异步函数获取返回值,等待未按预期工作(Vue API 服务)