介绍
通常,应用程序需要能够与外部 API 和数据库进行交互。随之而来的问题是,在等待某些请求和操作完成时,处理以与编写顺序不同的顺序运行的代码。
在本文中,您将探索Dart(尤其是Flutter)如何处理异步请求。
先决条件
要完成本教程,您需要:
- 下载并安装Flutter。
- 下载并安装Android Studio 或 Visual Studio Code。
- 建议为您的代码编辑器安装插件:
本教程通过 Flutter v2.0.6、Android SDK v31.0.2、Android Studio v4.1 验证。
理解异步代码
使用同步代码,当我们向外部 API 发送信息请求时,需要一些时间才能得到响应。我们的机器将等待它完成,停止可能与初始请求无关的事情。问题是我们不希望我们的脚本在每次需要一段时间时停止运行,但我们也不希望它过早运行依赖于返回数据的任何东西,尽管请求成功,这可能会导致错误。
两全其美的方式是设置我们的逻辑,让我们的机器在等待请求返回时提前工作,而只让依赖于该请求的代码在可用时运行。
我们的应用程序的数据很可能是四种形式之一,具体取决于它们是否已经可用以及它们是否是单一的。
这个例子将探索 Futures 和 Streams。
设置项目
为 Flutter 设置环境后,您可以运行以下命令来创建新应用程序:
- flutter create flutter_futures_example
导航到新的项目目录:
- cd flutter_futures_example
使用flutter create
将生成一个演示应用程序,该应用程序将显示单击按钮的次数。
此示例的一部分依赖于REST Country API。如果您提供国家/地区名称,此 API 将返回有关国家/地区的信息。例如,这是一个请求Canada
:
https://restcountries.eu/rest/v2/name/Canada
这也需要http
包。
pubspec.yaml
在您的代码编辑器中打开并添加以下插件:
dependencies:
flutter:
sdk: flutter
http: 0.13.3
现在,main.dart
在您的代码编辑器中打开并修改以下代码行以显示“获取国家/地区”按钮:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
void getCountry() {}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MaterialButton(
onPressed: () => getCountry(),
child: Container(
color: Colors.blue,
padding: EdgeInsets.all(15),
child: Text('Get Country', style: TextStyle(color: Colors.white))
),
),
),
);
}
}
此时,您有了一个带有http
包的新 Flutter 项目。
使用then
和catchError
与JavaScript 中的 try…catch非常相似,Dart 允许我们将方法链接在一起,这样我们就可以轻松地将返回数据从一个传递到另一个,它甚至返回一种类似于 Promise 的数据类型,称为 Futures。期货是任何单一类型的数据,如字符串,稍后可用。
要使用此技术,请执行您的操作,然后.then
将我们作为参数传入的返回数据链接起来,然后根据需要使用它。此时,您可以继续链接其他.then
. 对于错误处理,.catchError
在最后使用 a并抛出传递给它的任何内容。
main.dart
使用您的代码编辑器重新访问并使用.then
和.catchError
。首先,替换void GetCountry() {}
为Future GetCountry(country)
. 然后,将国家名称添加到onPressed: () => GetCountry()
:
// ...
class MyHomePage extends StatelessWidget {
Future getCountry(country) {
Uri countryUrl = Uri.http('restcountries.eu', '/rest/v2/name/$country');
http
.get(countryUrl)
.then((response) => jsonDecode(response.body)[0]['name'])
.then((decoded) => print(decoded))
.catchError((error) => throw(error));
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MaterialButton(
onPressed: () => getCountry('Canada'),
child: Container(
color: Colors.blue,
padding: EdgeInsets.all(15),
child: Text('Get Country', style: TextStyle(color: Colors.white))
),
),
),
);
}
}
保存更改并在模拟器中运行应用程序。然后,单击获取国家/地区按钮。您的控制台将记录国家/地区名称。
使用async
和await
许多人认为可读性很强的另一种语法是 Async/Await。
Async/Await 的工作方式与JavaScript完全相同,我们async
在函数名称之后使用关键字,并await
在需要一些时间运行的任何内容之前添加关键字,例如我们的 get 请求。
main.dart
使用您的代码编辑器重新访问并使用async
和await
。现在,当返回一个值时,它之后的所有内容都将运行。对于错误处理,我们可以在 try/catch 块中抛出错误。
// ...
class MyHomePage extends StatelessWidget {
Future getCountry(country) async {
Uri countryUrl = Uri.http('restcountries.eu', '/rest/v2/name/$country');
try {
http.Response response = await http.get(countryUrl);
Object decoded = jsonDecode(response.body)[0]['name'];
print(decoded);
} catch (e) { throw(e); }
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: MaterialButton(
onPressed: () => getCountry('Canada'),
child: Container(
color: Colors.blue,
padding: EdgeInsets.all(15),
child: Text('Get Country', style: TextStyle(color: Colors.white))
),
),
),
);
}
}
保存更改并在模拟器中运行应用程序。然后,单击获取国家/地区按钮。您的控制台将记录国家/地区名称。
使用流
Dart 的特别之处在于当我们有许多值被异步加载时,它会使用 Streams。不像我们的 GET 请求那样打开一次连接,我们可以让它保持打开状态并为新数据做好准备。
由于我们的示例通过设置允许 Streams 的后端而变得有点过于复杂,例如使用 Firebase或 GraphQL,我们将通过每秒发出一条新“消息”来模拟聊天应用程序数据库中的更改。
我们可以用这个StreamController
类创建一个 Stream ,它的工作方式类似于 a List
,因为它的行为就像一个 Futures 列表。
我们可以使用 on stream
、 likelisten
和属性来控制我们的 Streamclose
来启动和停止它。
警告:close()
当您的小部件被删除时,始终使用很重要。流将持续运行,直到它们被关闭,即使原始小部件消失,也会消耗计算能力。
现在,main.dart
在您的代码编辑器中打开并替换以下代码行以导入dart:async
和使用StatefulWidget
:
import 'package:flutter/material.dart';
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StreamController<String> streamController = StreamController();
void newMessage(int number, String message) {
final duration = Duration(seconds: number);
Timer.periodic(duration, (Timer t) => streamController.add(message));
}
void initState() {
super.initState();
streamController.stream.listen((messages) => print(messages));
newMessage(1, 'You got a message!');
}
void dispose() {
streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
padding: EdgeInsets.all(15),
child: Text('Streams Example'),
),
),
);
}
}
此代码将You got a message
在控制台中不断打印出来。
标准流可能会受到一些限制,因为它们一次只允许一个侦听器。相反,我们可以使用类broadcast
上的StreamController
属性来打开多个通道。
// ...
StreamController<String> streamController = StreamController.broadcast();
// ...
void initState() {
super.initState();
streamController.stream.listen((messages) => print('$messages - First'));
streamController.stream.listen((messages) => print('$messages - Second'));
newMessage(1, 'You got a message!');
}
// ...
此代码将连续打印出来You got a message - First
,并You got a message - Second
在控制台中。
结论
在本文中,您探索了 Dart(尤其是 Flutter)如何处理异步请求。Dart 中的异步编程将允许您开始开发智能和动态应用程序。
如果您想了解有关 Flutter 的更多信息,请查看我们的 Flutter 主题页面以获取练习和编程项目。