目录

“异步编程:future、async、await”

本教程教你如何使用 future 和 asyncawait 关键字编写异步代码。使用嵌入式 DartPad 编辑器,你可以通过运行示例代码和完成练习来测试你的知识。

要充分利用本教程,你应该具备以下知识:

本教程涵盖以下内容:

  • 如何以及何时使用 asyncawait 关键字。
  • 使用 asyncawait 如何影响执行顺序。
  • 如何使用 async 函数中的 try-catch 表达式处理异步调用的错误。

完成本教程的预计时间:40-60 分钟。

本教程中的练习包含部分完成的代码片段。你可以使用 DartPad 通过完成代码并点击 运行 按钮来测试你的知识。 不要编辑 main 函数或下面的测试代码

如果需要帮助,请在每个练习后展开 提示解答 下拉菜单。

为什么异步代码很重要

#

异步操作允许你的程序在等待另一个操作完成时完成工作。以下是一些常见的异步操作:

  • 通过网络获取数据。
  • 写入数据库。
  • 从文件读取数据。

此类异步计算通常将其结果作为 Future 提供,如果结果有多个部分,则作为 Stream 提供。这些计算将异步性引入程序。为了适应这种初始异步性,其他普通 Dart 函数也需要变成异步的。

为了与这些异步结果交互,你可以使用 asyncawait 关键字。大多数异步函数只是异步 Dart 函数,这些函数可能最终依赖于固有的异步计算。

示例:不正确地使用异步函数

#

以下示例显示了使用异步函数 (fetchUserOrder()) 的错误方法。稍后你将使用 asyncawait 来修复此示例。在运行此示例之前,尝试找出问题——你认为输出会是什么?

// 此示例显示了 *不* 应如何编写异步 Dart 代码。

String createOrderMessage() {
  var order = fetchUserOrder();
  return 'Your order is: $order';
}

Future<String> fetchUserOrder() =>
    // 想象一下,此函数更复杂且速度较慢。
    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(使用 asyncawait )的信息,以便能够编写必要的代码以使 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。因为它不返回可用值,所以 fetchUserOrder() 的类型为 Future<void> 。在你运行此示例之前,尝试预测哪个会先打印:“Large Latte”还是“Fetching user order...”?

Future<void> fetchUserOrder() {
  // 想象一下,此函数正在从另一个服务或数据库中获取用户信息。
  return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
}

void main() {
  fetchUserOrder();
  print('Fetching user order...');
}

在前面的示例中,即使 fetchUserOrder() 在第 8 行的 print() 调用之前执行,控制台也会在 fetchUserOrder() 的输出(“Large Latte”)之前显示第 8 行的输出(“Fetching user order...”)。这是因为 fetchUserOrder() 在打印“Large Latte”之前会延迟。

示例:使用错误完成

#

运行以下示例以查看 future 如何使用错误完成。稍后你将学习如何处理错误。

Future<void> fetchUserOrder() {
  // 想象一下,此函数正在获取用户信息,但遇到错误。
  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 以及它们是如何完成的,但是你如何使用异步函数的结果呢?在下一节中,你将学习如何使用 asyncawait 关键字获取结果。

使用 future:async 和 await

#

asyncawait 关键字提供了一种声明式的方法来定义异步函数和使用它们的结果。使用 asyncawait 时,请记住这两个基本准则:

  • 要定义异步函数,请在函数体之前添加 async
  • await 关键字仅可在 async 函数中使用

以下是一个将 main() 从同步函数转换为异步函数的示例。

首先,在函数体之前添加 async 关键字:

dart
void main() async { ··· }

如果函数具有声明的返回类型,则将类型更新为 Future<T> ,其中 T 是函数返回的值的类型。如果函数没有显式返回值,则返回类型为 Future<void>

dart
Future<void> main() async { ··· }

现在你有了 async 函数,你可以使用 await 关键字来等待 future 完成:

dart
print(await createOrderMessage());

如下面的两个示例所示, asyncawait 关键字生成的异步代码看起来非常像同步代码。唯一的区别在异步示例中突出显示,如果你的窗口足够宽,则位于同步示例的右侧。

示例:同步函数

#
dart
String createOrderMessage() {
  var order = fetchUserOrder();
  return 'Your order is: $order';
}

Future<String> fetchUserOrder() =>
    // 想象一下,此函数更复杂且速度较慢。
    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>'

如下面的两个示例所示,它像同步代码一样运行。

示例:异步函数

#
dart
Future<String> createOrderMessage() async {
  var order = await fetchUserOrder();
  return 'Your order is: $order';
}

Future<String> fetchUserOrder() =>
    // 想象一下,此函数更复杂且速度较慢。
    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() {
  // 想象一下,此函数更复杂且速度较慢。
  return Future.delayed(const Duration(seconds: 4), () => 'Large Latte');
}

void main() async {
  countSeconds(4);
  await printOrderMessage();
}

// 你可以忽略此函数——它在这里是为了在这个示例中可视化延迟时间。
void countSeconds(int s) {
  for (var i = 1; i <= s; i++) {
    Future.delayed(Duration(seconds: i), () => print(i));
  }
}

在运行前面示例中的代码后,尝试反转第 2 行和第 3 行:

dart
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: <user role>
    • 注意:你必须使用 fetchRole() 返回的实际值;复制粘贴示例返回值将无法使测试通过。
    • 示例返回值:“ User role: tester
  • 通过调用提供的函数 fetchRole() 获取用户角色。

第 2 部分: reportLogins()

#

实现一个 async 函数 reportLogins() ,使其执行以下操作:

  • 返回字符串:“ Total number of logins: <# of logins> ”。
    • 注意:你必须使用 fetchLoginAmount() 返回的实际值;复制粘贴示例返回值将无法使测试通过。
    • reportLogins() 的示例返回值:“ Total number of logins: 57
  • 通过调用提供的函数 fetchLoginAmount() 获取登录次数。
// 第 1 部分
// 调用提供的异步函数 fetchRole()
// 返回用户角色。
Future<String> reportUserRole() async {
  // TODO:在此处实现 reportUserRole 函数。
}

// 第 2 部分
// TODO:在此处实现 reportLogins 函数。
// 调用提供的异步函数 fetchLoginAmount()
// 返回用户登录的次数。
reportLogins() {}

// 以下函数为你提供,用于模拟
// 可能需要一段时间才能完成的异步操作。

Future<String> fetchRole() => Future.delayed(_halfSecond, () => _role);
Future<int> fetchLoginAmount() => Future.delayed(_halfSecond, () => _logins);

// 以下代码用于测试并提供解决方案的反馈。
// 无需读取或修改它。

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

// 测试助手。
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';
  }
}

// 测试中使用的断言。
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

解答
dart
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:

dart
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() {
  // 想象一下,此函数更复杂。
  var str = Future.delayed(
      const Duration(seconds: 4),
      () => throw 'Cannot locate user order');
  return str;
}

void main() async {
  await printOrderMessage();
}

练习:练习处理错误

#

以下练习提供使用上一节中描述的方法处理异步代码错误的实践。为了模拟异步操作,你的代码将调用以下为你提供的函数:

函数名类型签名说明
fetchNewUsername()Future<String> fetchNewUsername()返回你可以用来替换旧用户名的新用户名。

使用 asyncawait 来实现一个异步 changeUsername() 函数,该函数执行以下操作:

  • 调用提供的异步函数 fetchNewUsername() 并返回其结果。
    • changeUsername() 的示例返回值:“ jane_smith_92
  • 捕获发生的任何错误并返回错误的字符串值。
// TODO:在此处实现 changeUsername。
changeUsername() {}

// 以下函数为你提供,用于模拟
// 可能需要一段时间才能完成并
// 可能抛出异常的异步操作。

Future<String> fetchNewUsername() =>
    Future.delayed(const Duration(milliseconds: 500), () => throw UserError());

class UserError implements Exception {
  @override
  String toString() => 'New username is invalid';
}

// 以下代码用于测试并提供解决方案的反馈。
// 无需读取或修改它。

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

// 测试助手。
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 语句 来捕获和处理错误。

解答
dart
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() 返回的字符串值。
// 第 1 部分
addHello(String user) {}

// 第 2 部分
// 调用提供的异步函数 fetchUsername()
// 返回用户名。
greetUser() {}

// 第 3 部分
// 调用提供的异步函数 logoutUser()
// 注销用户。
sayGoodbye() {}

// 以下函数为你提供,可在你的解决方案中使用。

Future<String> fetchUsername() => Future.delayed(_halfSecond, () => 'Jean');

Future<String> logoutUser() => Future.delayed(_halfSecond, _failOnce);

// 以下代码用于测试并提供解决方案的反馈。
// 无需读取或修改它。

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

// 测试助手。
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;
提示

greetUsersayGoodbye 函数应该是异步的,而 addHello 应该是一个普通的同步函数。

记住:你可以使用 try-catch 语句 来捕获和处理错误。

解答
dart
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:

接下来的步骤

#

恭喜你完成了本教程!如果你想了解更多信息,以下是一些后续学习建议: