异步编程:future、async、await
本教程教您如何使用 future 以及 async
和 await
关键字编写异步代码。通过使用嵌入式 DartPad 编辑器,您可以通过运行示例代码和完成练习来测试您的知识。
要充分利用本教程,您应该具备以下条件
- 了解基本的 Dart 语法。
- 在其他语言中编写异步代码的一些经验。
- 启用
discarded_futures
和unawaited_futures
lint 规则。
本教程涵盖以下内容
- 何时以及如何使用
async
和await
关键字。 - 使用
async
和await
如何影响执行顺序。 - 如何使用
async
函数中的try-catch
表达式处理异步调用的错误。
完成本教程的预计时间:40-60 分钟。
本教程中的练习包含部分完成的代码片段。您可以使用 DartPad 通过完成代码并单击“运行”按钮来测试您的知识。请勿编辑 main
函数或其下的测试代码。
如果您需要帮助,请在每个练习后展开提示或解决方案下拉列表。
为什么异步代码很重要
#异步操作允许您的程序在等待另一个操作完成时完成工作。以下是一些常见的异步操作
- 通过网络获取数据。
- 写入数据库。
- 从文件中读取数据。
此类异步计算通常将其结果作为 Future
提供,或者如果结果有多个部分,则作为 Stream
提供。这些计算将异步引入程序。为了适应初始异步,其他普通的 Dart 函数也需要变为异步。
要与这些异步结果交互,您可以使用 async
和 await
关键字。大多数异步函数只是依赖于(可能深层依赖)固有异步计算的异步 Dart 函数。
示例:不正确地使用异步函数
#以下示例显示了使用异步函数 (fetchUserOrder()
) 的错误方法。稍后您将使用 async
和 await
修复此示例。在运行此示例之前,请尝试找出问题所在 -- 您认为输出会是什么?
// This example shows how *not* to write asynchronous Dart code.
String createOrderMessage() {
var order = fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
// Imagine that this function is more complex and slow.
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
void main() {
print(createOrderMessage());
}
这就是为什么此示例未能打印 fetchUserOrder()
最终产生的值的原因
fetchUserOrder()
是一个异步函数,在延迟后会提供一个描述用户订单的字符串:"Large Latte"。- 要获取用户的订单,
createOrderMessage()
应该调用fetchUserOrder()
并等待它完成。因为createOrderMessage()
*不*等待fetchUserOrder()
完成,所以createOrderMessage()
未能获取fetchUserOrder()
最终提供的字符串值。 - 相反,
createOrderMessage()
获取的是待完成工作的表示形式:未完成的 future。您将在下一节中了解有关 future 的更多信息。 - 因为
createOrderMessage()
未能获取描述用户订单的值,所以该示例未能向控制台打印 "Large Latte",而是打印 "Your order is: Instance of '_Future'"。
在接下来的部分中,您将了解 future 以及如何使用 future(使用 async
和 await
),以便您能够编写必要的代码,使 fetchUserOrder()
将所需的值("Large Latte")打印到控制台。
什么是 future?
#future(小写 “f”)是 Future(大写 “F”)类的实例。future 表示异步操作的结果,并且可以具有两种状态:未完成或已完成。
未完成
#当您调用异步函数时,它会返回一个未完成的 future。该 future 正在等待函数的异步操作完成或抛出错误。
已完成
#如果异步操作成功,则 future 会以值完成。否则,它会以错误完成。
以值完成
#类型为 Future<T>
的 future 会以类型为 T
的值完成。例如,类型为 Future<String>
的 future 会产生一个字符串值。如果 future 没有产生有用的值,则 future 的类型为 Future<void>
。
以错误完成
#如果函数执行的异步操作因任何原因失败,则 future 会以错误完成。
示例:引入 future
#在以下示例中,fetchUserOrder()
返回一个 future,该 future 在打印到控制台后完成。因为它不返回有用的值,所以 fetchUserOrder()
的类型为 Future<void>
。在运行示例之前,请尝试预测哪个会先打印:“Large Latte” 还是 “Fetching user order...”?
Future<void> fetchUserOrder() {
// Imagine that this function is fetching user info from another service or database.
return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
在前面的示例中,即使 fetchUserOrder()
在第 8 行的 print()
调用之前执行,控制台也首先显示第 8 行(“Fetching user order...”)的输出,然后显示 fetchUserOrder()
的输出(“Large Latte”)。这是因为 fetchUserOrder()
会延迟,然后再打印 “Large Latte”。
示例:以错误完成
#运行以下示例,查看 future 如何以错误完成。稍后您将学习如何处理错误。
Future<void> fetchUserOrder() {
// Imagine that this function is fetching user info but encounters a bug.
return Future.delayed(
const Duration(seconds: 2),
() => throw Exception('Logout failed: user ID is invalid'),
);
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
在此示例中,fetchUserOrder()
以错误完成,表示用户 ID 无效。
您已经了解了 future 以及它们如何完成,但是如何使用异步函数的结果呢?在下一节中,您将学习如何使用 async
和 await
关键字获取结果。
使用 future:async 和 await
#async
和 await
关键字提供了一种声明式方式来定义异步函数并使用其结果。使用 async
和 await
时,请记住以下两个基本准则
- 要定义异步函数,请在函数体之前添加
async
await
关键字仅在async
函数中起作用。
以下示例将 main()
从同步函数转换为异步函数。
首先,在函数体之前添加 async
关键字
void main() async { ··· }
如果函数声明了返回类型,则将类型更新为 Future<T>
,其中 T
是函数返回的值的类型。如果函数没有显式返回值,则返回类型为 Future<void>
。
Future<void> main() async { ··· }
现在你拥有了一个 async
函数,可以使用 await
关键字来等待 Future 完成。
print(await createOrderMessage());
如下面两个示例所示,async
和 await
关键字使异步代码看起来很像同步代码。唯一的区别在异步示例中突出显示,如果你的窗口足够宽,它位于同步示例的右侧。
示例:同步函数
#String createOrderMessage() {
var order = fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
// Imagine that this function is
// more complex and slow.
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
void main() {
print('Fetching user order...');
print(createOrderMessage());
}
Fetching user order...
Your order is: Instance of 'Future<String>'
如下面两个示例所示,它的操作方式类似于同步代码。
示例:异步函数
#Future<String> createOrderMessage() async {
var order = await fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
// Imagine that this function is
// more complex and slow.
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
Future<void> main() async {
print('Fetching user order...');
print(await createOrderMessage());
}
Fetching user order...
Your order is: Large Latte
异步示例在三个方面有所不同
createOrderMessage()
的返回类型从String
更改为Future<String>
。async
关键字出现在createOrderMessage()
和main()
的函数体之前。await
关键字出现在调用异步函数fetchUserOrder()
和createOrderMessage()
之前。
使用 async 和 await 的执行流程
#一个 async
函数会同步运行,直到遇到第一个 await
关键字。这意味着在 async
函数体中,第一个 await
关键字之前的所有同步代码都会立即执行。
示例:async 函数内的执行
#运行以下示例,查看 async
函数体内的执行过程。你认为输出会是什么?
Future<void> printOrderMessage() async {
print('Awaiting user order...');
var order = await fetchUserOrder();
print('Your order is: $order');
}
Future<String> fetchUserOrder() {
// Imagine that this function is more complex and slow.
return Future.delayed(const Duration(seconds: 4), () => 'Large Latte');
}
void main() async {
countSeconds(4);
await printOrderMessage();
}
// You can ignore this function - it's here to visualize delay time in this example.
void countSeconds(int s) {
for (var i = 1; i <= s; i++) {
Future.delayed(Duration(seconds: i), () => print(i));
}
}
在运行上一个示例中的代码后,尝试反转第 2 行和第 3 行
var order = await fetchUserOrder();
print('Awaiting user order...');
请注意,输出的时间发生了变化,现在 print('Awaiting user order')
出现在 printOrderMessage()
中第一个 await
关键字之后。
练习:练习使用 async 和 await
#以下练习是一个失败的单元测试,其中包含部分完成的代码片段。你的任务是通过编写代码使测试通过来完成练习。你不需要实现 main()
。
要模拟异步操作,请调用以下为你提供的函数
函数 | 类型签名 | 描述 |
---|---|---|
fetchRole() | Future<String> fetchRole() | 获取用户角色的简短描述。 |
fetchLoginAmount() | Future<int> fetchLoginAmount() | 获取用户登录的次数。 |
第 1 部分:reportUserRole()
#向 reportUserRole()
函数添加代码,使其执行以下操作
- 返回一个 Future,该 Future 完成时返回以下字符串:
"User role: <用户角色>"
- 注意:你必须使用
fetchRole()
返回的实际值;复制和粘贴示例返回值不会使测试通过。 - 示例返回值:
"User role: tester"
- 注意:你必须使用
- 通过调用提供的函数
fetchRole()
获取用户角色。
第 2 部分:reportLogins()
#实现一个 async
函数 reportLogins()
,使其执行以下操作
- 返回字符串
"Total number of logins: <登录次数>"
。- 注意:你必须使用
fetchLoginAmount()
返回的实际值;复制和粘贴示例返回值不会使测试通过。 - 来自
reportLogins()
的示例返回值:"Total number of logins: 57"
- 注意:你必须使用
- 通过调用提供的函数
fetchLoginAmount()
获取登录次数。
// Part 1
// Call the provided async function fetchRole()
// to return the user role.
Future<String> reportUserRole() async {
// TODO: Implement the reportUserRole function here.
}
// Part 2
// TODO: Implement the reportLogins function here.
// Call the provided async function fetchLoginAmount()
// to return the number of times that the user has logged in.
reportLogins() {}
// The following functions those provided to you to simulate
// asynchronous operations that could take a while.
Future<String> fetchRole() => Future.delayed(_halfSecond, () => _role);
Future<int> fetchLoginAmount() => Future.delayed(_halfSecond, () => _logins);
// The following code is used to test and provide feedback on your solution.
// There is no need to read or modify it.
void main() async {
print('Testing...');
List<String> messages = [];
const passed = 'PASSED';
const testFailedMessage = 'Test failed for the function:';
const typoMessage = 'Test failed! Check for typos in your return value';
try {
messages
..add(_makeReadable(
testLabel: 'Part 1',
testResult: await _asyncEquals(
expected: 'User role: administrator',
actual: await reportUserRole(),
typoKeyword: _role,
),
readableErrors: {
typoMessage: typoMessage,
'null':
'Test failed! Did you forget to implement or return from reportUserRole?',
'User role: Instance of \'Future<String>\'':
'$testFailedMessage reportUserRole. Did you use the await keyword?',
'User role: Instance of \'_Future<String>\'':
'$testFailedMessage reportUserRole. Did you use the await keyword?',
'User role:':
'$testFailedMessage reportUserRole. Did you return a user role?',
'User role: ':
'$testFailedMessage reportUserRole. Did you return a user role?',
'User role: tester':
'$testFailedMessage reportUserRole. Did you invoke fetchRole to fetch the user\'s role?',
}))
..add(_makeReadable(
testLabel: 'Part 2',
testResult: await _asyncEquals(
expected: 'Total number of logins: 42',
actual: await reportLogins(),
typoKeyword: _logins.toString(),
),
readableErrors: {
typoMessage: typoMessage,
'null':
'Test failed! Did you forget to implement or return from reportLogins?',
'Total number of logins: Instance of \'Future<int>\'':
'$testFailedMessage reportLogins. Did you use the await keyword?',
'Total number of logins: Instance of \'_Future<int>\'':
'$testFailedMessage reportLogins. Did you use the await keyword?',
'Total number of logins: ':
'$testFailedMessage reportLogins. Did you return the number of logins?',
'Total number of logins:':
'$testFailedMessage reportLogins. Did you return the number of logins?',
'Total number of logins: 57':
'$testFailedMessage reportLogins. Did you invoke fetchLoginAmount to fetch the number of user logins?',
}))
..removeWhere((m) => m.contains(passed))
..toList();
if (messages.isEmpty) {
print('Success. All tests passed!');
} else {
messages.forEach(print);
}
} on UnimplementedError {
print(
'Test failed! Did you forget to implement or return from reportUserRole?');
} catch (e) {
print('Tried to run solution, but received an exception: $e');
}
}
const _role = 'administrator';
const _logins = 42;
const _halfSecond = Duration(milliseconds: 500);
// Test helpers.
String _makeReadable({
required String testResult,
required Map<String, String> readableErrors,
required String testLabel,
}) {
if (readableErrors.containsKey(testResult)) {
var readable = readableErrors[testResult];
return '$testLabel $readable';
} else {
return '$testLabel $testResult';
}
}
// Assertions used in tests.
Future<String> _asyncEquals({
required String expected,
required dynamic actual,
required String typoKeyword,
}) async {
var strActual = actual is String ? actual : actual.toString();
try {
if (expected == actual) {
return 'PASSED';
} else if (strActual.contains(typoKeyword)) {
return 'Test failed! Check for typos in your return value';
} else {
return strActual;
}
} catch (e) {
return e.toString();
}
}
提示
你是否记得向 reportUserRole
函数添加 async
关键字?
你是否记得在调用 fetchRole()
之前使用 await
关键字?
记住:reportUserRole
需要返回一个 Future
。
解决方案
Future<String> reportUserRole() async {
final username = await fetchRole();
return 'User role: $username';
}
Future<String> reportLogins() async {
final logins = await fetchLoginAmount();
return 'Total number of logins: $logins';
}
处理错误
#要在 async
函数中处理错误,请使用 try-catch
try {
print('Awaiting user order...');
var order = await fetchUserOrder();
} catch (err) {
print('Caught error: $err');
}
在 async
函数中,你可以像在同步代码中一样编写 try-catch 子句。
示例:使用 try-catch 的 async 和 await
#运行以下示例,查看如何处理来自异步函数的错误。你认为输出会是什么?
Future<void> printOrderMessage() async {
try {
print('Awaiting user order...');
var order = await fetchUserOrder();
print(order);
} catch (err) {
print('Caught error: $err');
}
}
Future<String> fetchUserOrder() {
// Imagine that this function is more complex.
var str = Future.delayed(
const Duration(seconds: 4),
() => throw 'Cannot locate user order');
return str;
}
void main() async {
await printOrderMessage();
}
练习:练习处理错误
#以下练习提供了使用上一节中描述的方法处理异步代码中的错误的练习。为了模拟异步操作,你的代码将调用以下为你提供的函数
函数 | 类型签名 | 描述 |
---|---|---|
fetchNewUsername() | Future<String> fetchNewUsername() | 返回可用于替换旧用户名的新用户名。 |
使用 async
和 await
来实现一个异步 changeUsername()
函数,该函数执行以下操作
- 调用提供的异步函数
fetchNewUsername()
并返回其结果。- 来自
changeUsername()
的示例返回值:"jane_smith_92"
- 来自
- 捕获发生的任何错误并返回错误的字符串值。
- 你可以使用 toString() 方法来字符串化 异常 和 错误。
// TODO: Implement changeUsername here.
changeUsername() {}
// The following function is provided to you to simulate
// an asynchronous operation that could take a while and
// potentially throw an exception.
Future<String> fetchNewUsername() =>
Future.delayed(const Duration(milliseconds: 500), () => throw UserError());
class UserError implements Exception {
@override
String toString() => 'New username is invalid';
}
// The following code is used to test and provide feedback on your solution.
// There is no need to read or modify it.
void main() async {
final List<String> messages = [];
const typoMessage = 'Test failed! Check for typos in your return value';
print('Testing...');
try {
messages
..add(_makeReadable(
testLabel: '',
testResult: await _asyncDidCatchException(changeUsername),
readableErrors: {
typoMessage: typoMessage,
_noCatch:
'Did you remember to call fetchNewUsername within a try/catch block?',
}))
..add(_makeReadable(
testLabel: '',
testResult: await _asyncErrorEquals(changeUsername),
readableErrors: {
typoMessage: typoMessage,
_noCatch:
'Did you remember to call fetchNewUsername within a try/catch block?',
}))
..removeWhere((m) => m.contains(_passed))
..toList();
if (messages.isEmpty) {
print('Success. All tests passed!');
} else {
messages.forEach(print);
}
} catch (e) {
print('Tried to run solution, but received an exception: $e');
}
}
// Test helpers.
String _makeReadable({
required String testResult,
required Map<String, String> readableErrors,
required String testLabel,
}) {
if (readableErrors.containsKey(testResult)) {
final readable = readableErrors[testResult];
return '$testLabel $readable';
} else {
return '$testLabel $testResult';
}
}
Future<String> _asyncErrorEquals(Function fn) async {
final result = await fn();
if (result == UserError().toString()) {
return _passed;
} else {
return 'Test failed! Did you stringify and return the caught error?';
}
}
Future<String> _asyncDidCatchException(Function fn) async {
var caught = true;
try {
await fn();
} on UserError catch (_) {
caught = false;
}
if (caught == false) {
return _noCatch;
} else {
return _passed;
}
}
const _passed = 'PASSED';
const _noCatch = 'NO_CATCH';
提示
实现 changeUsername
以返回来自 fetchNewUsername
的字符串,或者,如果该操作失败,则返回发生的任何错误的字符串值。
记住:你可以使用 try-catch 语句来捕获和处理错误。
解决方案
Future<String> changeUsername() async {
try {
return await fetchNewUsername();
} catch (err) {
return err.toString();
}
}
练习:将所有内容组合在一起
#现在是时候在最后一个练习中练习你所学到的知识了。为了模拟异步操作,此练习提供了异步函数 fetchUsername()
和 logoutUser()
函数 | 类型签名 | 描述 |
---|---|---|
fetchUsername() | Future<String> fetchUsername() | 返回与当前用户关联的名称。 |
logoutUser() | Future<String> logoutUser() | 执行当前用户的注销并返回注销的用户名。 |
编写以下内容
第 1 部分:addHello()
#- 编写一个函数
addHello()
,该函数接受一个String
参数。 addHello()
返回其String
参数,并在其前面加上'Hello '
。
示例:addHello('Jon')
返回'Hello Jon'
。
第 2 部分:greetUser()
#- 编写一个不带参数的函数
greetUser()
。 - 为了获取用户名,
greetUser()
调用提供的异步函数fetchUsername()
。 greetUser()
通过调用addHello()
,传递用户名并返回结果来创建对用户的问候。
示例:如果fetchUsername()
返回'Jenny'
,则greetUser()
返回'Hello Jenny'
。
第 3 部分:sayGoodbye()
#- 编写一个函数
sayGoodbye()
,该函数执行以下操作- 不带参数。
- 捕获任何错误。
- 调用提供的异步函数
logoutUser()
。
- 如果
logoutUser()
失败,sayGoodbye()
会返回你喜欢的任何字符串。 - 如果
logoutUser()
成功,sayGoodbye()
会返回字符串'<result> Thanks, see you next time'
,其中<result>
是通过调用logoutUser()
返回的字符串值。
// Part 1
addHello(String user) {}
// Part 2
// Call the provided async function fetchUsername()
// to return the username.
greetUser() {}
// Part 3
// Call the provided async function logoutUser()
// to log out the user.
sayGoodbye() {}
// The following functions are provided to you to use in your solutions.
Future<String> fetchUsername() => Future.delayed(_halfSecond, () => 'Jean');
Future<String> logoutUser() => Future.delayed(_halfSecond, _failOnce);
// The following code is used to test and provide feedback on your solution.
// There is no need to read or modify it.
void main() async {
const didNotImplement =
'Test failed! Did you forget to implement or return from';
final List<String> messages = [];
print('Testing...');
try {
messages
..add(_makeReadable(
testLabel: 'Part 1',
testResult: await _asyncEquals(
expected: 'Hello Jerry',
actual: addHello('Jerry'),
typoKeyword: 'Jerry'),
readableErrors: {
_typoMessage: _typoMessage,
'null': '$didNotImplement addHello?',
'Hello Instance of \'Future<String>\'':
'Looks like you forgot to use the \'await\' keyword!',
'Hello Instance of \'_Future<String>\'':
'Looks like you forgot to use the \'await\' keyword!',
}))
..add(_makeReadable(
testLabel: 'Part 2',
testResult: await _asyncEquals(
expected: 'Hello Jean',
actual: await greetUser(),
typoKeyword: 'Jean'),
readableErrors: {
_typoMessage: _typoMessage,
'null': '$didNotImplement greetUser?',
'HelloJean':
'Looks like you forgot the space between \'Hello\' and \'Jean\'',
'Hello Instance of \'Future<String>\'':
'Looks like you forgot to use the \'await\' keyword!',
'Hello Instance of \'_Future<String>\'':
'Looks like you forgot to use the \'await\' keyword!',
'{Closure: (String) => dynamic from Function \'addHello\': static.(await fetchUsername())}':
'Did you place the \'\$\' character correctly?',
'{Closure \'addHello\'(await fetchUsername())}':
'Did you place the \'\$\' character correctly?',
}))
..add(_makeReadable(
testLabel: 'Part 3',
testResult: await _asyncDidCatchException(sayGoodbye),
readableErrors: {
_typoMessage:
'$_typoMessage. Did you add the text \'Thanks, see you next time\'?',
'null': '$didNotImplement sayGoodbye?',
_noCatch:
'Did you remember to call logoutUser within a try/catch block?',
'Instance of \'Future<String>\' Thanks, see you next time':
'Did you remember to use the \'await\' keyword in the sayGoodbye function?',
'Instance of \'_Future<String>\' Thanks, see you next time':
'Did you remember to use the \'await\' keyword in the sayGoodbye function?',
}))
..add(_makeReadable(
testLabel: 'Part 3',
testResult: await _asyncEquals(
expected: 'Success! Thanks, see you next time',
actual: await sayGoodbye(),
typoKeyword: 'Success'),
readableErrors: {
_typoMessage:
'$_typoMessage. Did you add the text \'Thanks, see you next time\'?',
'null': '$didNotImplement sayGoodbye?',
_noCatch:
'Did you remember to call logoutUser within a try/catch block?',
'Instance of \'Future<String>\' Thanks, see you next time':
'Did you remember to use the \'await\' keyword in the sayGoodbye function?',
'Instance of \'_Future<String>\' Thanks, see you next time':
'Did you remember to use the \'await\' keyword in the sayGoodbye function?',
'Instance of \'_Exception\'':
'CAUGHT Did you remember to return a string?',
}))
..removeWhere((m) => m.contains(_passed))
..toList();
if (messages.isEmpty) {
print('Success. All tests passed!');
} else {
messages.forEach(print);
}
} catch (e) {
print('Tried to run solution, but received an exception: $e');
}
}
// Test helpers.
String _makeReadable({
required String testResult,
required Map<String, String> readableErrors,
required String testLabel,
}) {
String? readable;
if (readableErrors.containsKey(testResult)) {
readable = readableErrors[testResult];
return '$testLabel $readable';
} else if ((testResult != _passed) && (testResult.length < 18)) {
readable = _typoMessage;
return '$testLabel $readable';
} else {
return '$testLabel $testResult';
}
}
Future<String> _asyncEquals({
required String expected,
required dynamic actual,
required String typoKeyword,
}) async {
final strActual = actual is String ? actual : actual.toString();
try {
if (expected == actual) {
return _passed;
} else if (strActual.contains(typoKeyword)) {
return _typoMessage;
} else {
return strActual;
}
} catch (e) {
return e.toString();
}
}
Future<String> _asyncDidCatchException(Function fn) async {
var caught = true;
try {
await fn();
} on Exception catch (_) {
caught = false;
}
if (caught == true) {
return _passed;
} else {
return _noCatch;
}
}
const _typoMessage = 'Test failed! Check for typos in your return value';
const _passed = 'PASSED';
const _noCatch = 'NO_CATCH';
const _halfSecond = Duration(milliseconds: 500);
String _failOnce() {
if (_logoutSucceeds) {
return 'Success!';
} else {
_logoutSucceeds = true;
throw Exception('Logout failed');
}
}
bool _logoutSucceeds = false;
解决方案
String addHello(String user) => 'Hello $user';
Future<String> greetUser() async {
final username = await fetchUsername();
return addHello(username);
}
Future<String> sayGoodbye() async {
try {
final result = await logoutUser();
return '$result Thanks, see you next time';
} catch (e) {
return 'Failed to logout user: $e';
}
}
哪些 lint 规则适用于 future?
#为了捕获在使用 async 和 Future 时出现的常见错误,启用以下 lint
下一步是什么?
#恭喜,你已经完成本教程!如果你想了解更多信息,这里有一些关于下一步去向的建议
- 使用 DartPad 进行练习。
- 尝试另一个 教程。
- 了解有关 Dart 中 Future 和异步代码的更多信息
- 流教程:学习如何使用一系列异步事件。
- Dart 中的并发:了解并学习如何在 Dart 中实现并发。
- 异步支持:深入了解 Dart 的语言和库对异步编码的支持。
- 来自 Google 的 Dart 视频:观看一个或多个关于异步编码的视频。
- 获取 Dart SDK!
除非另有说明,否则本网站上的文档反映了 Dart 3.6.0。页面上次更新于 2024-11-17。查看源代码 或 报告问题。