Contents

Asynchronous programming: futures, async, await

本代码实验室教您如何使用Futures和asyncawait关键字编写异步代码. 使用嵌入式DartPad编辑器,您可以通过运行示例代码并完成练习来测试您的知识.

为了充分利用此代码实验室,您应该具备以下条件:

该代码实验室涵盖以下材料:

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

完成此代码实验室的估计时间:40-60分钟.

Why asynchronous code matters

异步操作使您的程序可以在等待另一个操作完成的同时完成工作. 以下是一些常见的异步操作:

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

要在Dart中执行异步操作,可以使用Future类以及asyncawait关键字.

Example: Incorrectly using an asynchronous function

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

这就是示例无法打印fetchUserOrder()最终产生的值的原因:

  • fetchUserOrder()是一个异步函数,在延迟后,它提供了一个描述用户顺序的字符串:"大拿铁".
  • 要获取用户的订单, createOrderMessage()应调用fetchUserOrder()并等待其完成. 由于createOrderMessage() 不会等待fetchUserOrder()来完成, createOrderMessage()未能得到字符串值fetchUserOrder()最终提供.
  • 相反, createOrderMessage()获得待完成的工作的表示:未完成的未来. 您将在下一部分中了解有关期货的更多信息.
  • 由于createOrderMessage()无法获取描述用户订单的值,因此该示例无法在控制台上打印" Large Latte",而是打印"您的订单是:'_ Future的实例 '".

在接下来的部分中,您将学习关于fetchUserOrder()asyncawait以便您能够编写必要的代码,以使fetchUserOrder()将所需的值("大拿铁")打印到控制台.

What is a future?

未来(小写的" f")是未来 (大写的" F")类的实例. 未来表示异步操作的结果,并且可以具有两种状态:未完成或已完成.

Uncompleted

当您调用异步函数时,它将返回未完成的将来. 那个未来正在等待函数的异步操作完成或引发错误.

Completed

如果异步操作成功,则将来将以一个值完成. 否则,它会以错误完成.

Completing with a value

类型Future<T>的类型值为T 例如,类型为Future<String>会产生一个字符串值. 如果期货未产生可用值,则该期货的类型为Future<void> .

Completing with an error

如果该函数执行的异步操作由于任何原因而失败,则将来会出现错误.

Example: Introducing futures

在以下示例中, fetchUserOrder()返回在打印到控制台后完成的fetchUserOrder() . 由于fetchUserOrder()不返回可用值,因此其类型为Future<void> . 在运行示例之前,请尝试预测将首先打印的内容:"大拿铁"或"获取用户订单…".

在前面的示例中,即使fetchUserOrder()在第8行的print()调用之前执行,控制台fetchUserOrder()fetchUserOrder() ("大拿铁")的输出之前显示第8行的输出("正在获取用户订单…"). . 这是因为fetchUserOrder()在打印"大拿铁"之前会延迟.

Example: Completing with an error

运行以下示例以查看将来如何完成并出现错误. 稍后,您将学习如何处理该错误.

在此示例中, fetchUserOrder()完成并显示一条错误,指示用户ID无效.

您已经了解了期货及其完成方式,但是如何使用异步函数的结果呢? 在下一节中,您将学习如何使用asyncawait关键字获得结果.

Working with futures: async and await

asyncawait关键字提供了一种声明性的方式来定义异步函数并使用其结果. 使用asyncawait时,请记住以下两个基本准则:

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

这是一个将main()从同步功能转换为异步功能的示例.

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

void main() async { ··· }

如果函数具有声明的返回类型,则将类型更新为Future<T> ,其中T是函数返回的值的类型. 如果该函数未明确返回值,则返回类型为Future<void>

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

现在您有了async功能,可以使用await关键字等待将来完成:

print(await createOrderMessage());

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

Example: synchronous functions

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

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

void main() {
  print('Fetching user order...');
  print(createOrderMessage());
}
Fetching user order...
Your order is: Instance of _Future<String>

Example: asynchronous functions

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(
      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()的函数体之前.
  • 在调用异步函数fetchUserOrder()createOrderMessage()之前,将出现await关键字.

Execution flow with async and await

async函数将同步运行,直到第一个await关键字. 这意味着在async函数体内,第一个await关键字之前的所有同步代码将立即执行.

Example: Execution within async functions

运行以下示例以查看执行如何在async函数体内进行. 您认为输出结果是什么?

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

var order = await fetchUserOrder();
print('Awaiting user order...');

请注意,输出的时间发生了变化,现在print('Awaiting user order')出现在printOrderMessage()的第一个await关键字之后.

Exercise: Practice using async and await

以下练习是一个失败的单元测试,其中包含部分完成的代码片段. 您的任务是通过编写代码使测试通过来完成练习. 您不需要实现main() .

要模拟异步操作,请调用为您提供的以下函数:

Function 类型签名 Description
fetchRole() Future<String> fetchRole() 获取用户角色的简短描述.
fetchLoginAmount() Future<int> fetchLoginAmount() 获取用户登录的次数.

Part 1: reportUserRole()

将代码添加到reportUserRole()函数,以便执行以下操作:

  • 返回以以下字符串结尾的Future: "User role: <user role>"
    • 注意:您必须使用fetchRole()返回的实际值; 复制并粘贴示例返回值不会使测试通过.
    • 返回值示例: "User role: tester"
  • 通过调用提供的函数fetchRole()获得用户角色.

Part 2: reportLogins()

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

  • 返回字符串"Total number of logins: <# of logins>" .
    • 注意:您必须使用fetchLoginAmount()返回的实际值; 复制并粘贴示例返回值不会使测试通过.
    • 示例来自reportLogins()返回值: "Total number of logins: 57"
  • 通过调用提供的函数fetchLoginAmount()获得登录次数.

Handling errors

要处理async函数中的错误,请使用try-catch:

try {
  var order = await fetchUserOrder();
  print('Awaiting user order...');
} catch (err) {
  print('Caught error: $err');
}

async函数中,您可以像在同步代码中一样编写try-catch子句 .

Example: async and await with try-catch

运行以下示例以查看如何处理异步函数中的错误. 您认为输出结果是什么?

Exercise: Practice handling errors

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

Function 类型签名 Description
fetchNewUsername() Future<String> fetchNewUsername() 返回可用于替换旧用户名的新用户名.

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

  • 调用提供的异步函数fetchNewUsername()并返回其结果.
    • changeUsername()返回值的示例: "jane_smith_92"
  • 捕获发生的任何错误并返回错误的字符串值.

Exercise: Putting it all together

现在是时候练习最后一次练习中学到的知识了. 为了模拟异步操作,此练习提供了异步函数fetchUsername()logoutUser()

Function 类型签名 Description
fetchUsername() Future<String> fetchUsername() 返回与当前用户关联的名称.
logoutUser() Future<String> logoutUser() Performs logout of current user and returns the username that was logged out.

编写以下内容:

Part 1: addHello()

  • 编写一个带有单个String参数的函数addHello() .
  • addHello()返回其字符串参数, addHello() " Hello".
    示例: addHello('Jon')返回'Hello Jon' .

Part 2: greetUser()

  • 编写一个不带参数的greetUser()函数.
  • 要获取用户名, greetUser()调用提供的异步函数fetchUsername() .
  • greetUser()通过调用addHello() ,为用户传递用户名并返回结果来为用户创建问候语.
    示例:如果fetchUsername()返回'Jenny' ,则greetUser()返回'Hello Jenny' .

Part 3: sayGoodbye()

  • 编写一个函数sayGoodbye() ,该函数执行以下操作:
    • 不带参数.
    • 捕获任何错误.
    • 调用提供的异步函数logoutUser() .
  • 如果logoutUser()失败, sayGoodbye()将返回您喜欢的任何字符串.
  • 如果logoutUser()成功,则sayGoodbye()返回字符串'<result> Thanks, see you next time' ,其中<result>是调用logoutUser()返回的字符串值.

What’s next?

恭喜,您已经完成了代码实验室! 如果您想了解更多信息,请参考以下建议:

如果您对使用嵌入式DartPad感兴趣(如本代码实验室一样),请在教程中查看有关使用DartPad的最佳实践 .

by  ICOPY.SITE