1. 主页
  2. 文档
  3. Dart 2 中文文档
  4. 示例 & 课程
  5. Codelabs
  6. 异步编程

异步编程

异步编程: futures, async, await

这个codelab教我们如何使用future和关键字async 及await来编写异步代码。使用内嵌的DartPad编辑器,可以通过运行示例代码和完成练习来测试所学的知识。

要通过codelab获取尽可能多的知识,需要具有如下:

  • 基础Dart语法的知识。
  • 一些在其它语言中编写异步代码的经验。

这个codelab涵盖如下内容:

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

完成这一codelab的预估时间: 40-60分钟。

为什么异步代码很重要

异步操作让程序在等待另一个操作完成时完成任务。以下是一些常见的异步操作:

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

要在Dart中执行异步操作,可以使用 Future 类和 async 及 await 关键字。

示例:错误使用异步函数

以下示例显示使用一个异步函数(getUserOrder())的错误方式。稍后你可以使用async和 await 来修复这一示例。在运行本例时,尝试定位问题,你认为输出会是什么?

以下是示例中打印getUserOrder()最终产生值失败的原因:

  • getUserOrder() 是一个异步函数,在个延时后会提供描述用户订单的字符串:Large Latte。
  • 要获取用户的订单,createOrderMessage() 应调用 getUserOrder() 并等待其完成。因为createOrderMessage()不会等待 getUserOrder() 完成, createOrderMessage() 就无法获取到 getUserOrder() 所最终产生的字符串值。
  • createOrderMessage() 获取一个等待任务完成的表示:一个未完成的 future.。在下一节中会学到有关future的更多内容。
  • 因为 createOrderMessage() 无法获取到描述用户订单的值,示例中无法在控制台中打印Large Latte,而是打印了Your order is: Instance of ‘_Future’。

在下一节中会学到有关future, async和 await的知识,这样你就能够编写出让 getUserOrder()在控制台打印出所期望值Large Latte所需的代码。

future是什么?

future (小写字母f)是一个 Future (大写字母 F)类的实例。 future代表异步操作的结果,并且可以有两种状态:已完成或未完成。

未完成

在调用异步函数时,它返回一个未完成的future。这个future等待函数的异步操作完成或抛出错误。

已完成

如果异步操作成功, future完成并生成值。否则它报错并完成。

完成并生成值

类型为Future<T>的future完成并生成类型为T的值。 例如,类型为 Future<String> 的future生成一个字符串值。如果 future不生成一个可用的值,那么 future的类型为Future<void>

报错并完成

如果由函数执行的异步操作会出于某种原因失败,future 会报错并完成。

示例: future简介

在下面的示例中, getUserOrder() 返回一个在打印到控制台后完成的future。因为它不返回可用的值,getUserOrder() 获取的类型为 Future<void>。在运行示例前,请尝试预测哪个会先打印:Large Latte 或是 Fetching user order…。

在上例中,虽然在第8行的print()调用前执行了getUserOrder(),控制台中的输出中第8行的Fetching user order…在getUserOrder()的输出之前出现 。这是因为在打印Large Latte之前getUserOrder()存在延时。

示例:报错并完成

运行下例来查看future如何报错并完成。稍晚你会学到如何处理错误。

本例中, getUserOrder() 报错并完成,表明用户的 ID 是无效的。

你已经学到了 future及它们如何完成,但如何使用异步函数的结果呢?下一节中将学习如何通过async 和 await关键字获取结果。

处理futures: async 和 await

关键字 async 和 await 提供一个声明方式来定义异步函数并使用它们的结果。在使用async 和 await时记住这两个基本指南:

  • 定义一个异步函数,在函数体之前添加 async 
  • await 关键字仅能在 async 函数中使用

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

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

如果函数声明了一个返回类型,那么更新该类型为 Future<T>,其中 T 是函数返回值的类型。如果函数没有显式地返回值,那么返回类型为 Future<void>

既然你有了一个 async 函数,可以使用 await 关键字来等待future完成:

如以下两个例子所示, async 和 await 关键字产生和同步代码非常相似的异步代码。不同之处在以异步代码中已高亮标出,如果屏幕够大的话,显示在同步示例的右侧。

示例:同步函数

示例:异步函数

异步示例中有3处不同:

  • createOrderMessage() 的返回类型由 String 变成了 Future<String>
  • async关键字出现在createOrderMessage() 和 main()的函数体之前。
  • await 关键字出现 在调用异步函数 getUserOrder() 和 createOrderMessage()之前。

使用async 和 await的执行流程

async 函数会按同步运行,直至出现第一个await关键字。 这表示在 async 函数体内,所有第一个await之前的同步代码都会立即执行。

示例:在async函数内执行

运行如下示例来查看async函数体中如何执行。你觉得输出会是什么呢?

在执行上例中的代码后,试着调换第4行和第5行:

注意输出的时机发生了变化,因为createOrderMessage()中的 print('Awaiting user order') 出现在第一个 await 关键字之后。

练习:练习使用async 和 await

以下的练习是一个会失败的单元测试,包含部分完成的代码脚本。你的任务是通过编写代码来让测试通过以完成练习。无需实现 main()

要模拟异步操作,调用如下已为你提供的函数:

函数 类型签名 描述
getRole() Future<String> getRole() 获取一个用户角色的短描述。
getLoginAmount() Future<int> getLoginAmount() 获取用户登录的次数。

Part 1: reportUserRole()

reportUserRole() 函数中添加代码完成如下操作:

  • 返回一个完成及获取"User role: <user role>"字符串的future
    • 注意: 必须使用 getRole()所返回的实际值;复制并粘贴示例返回值不会让测试通过。
    • 示例返回值:"User role: tester"
  • 通过调用所提供函数getRole()来获取用户角色。

Part 2: reportLogins()

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

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

处理错误

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

async 函数中,可以用同步代码中相同的方式编写 try-catch从句

示例:带有try-catch的async 和 await

运行如下示例来查看如何处理异步函数中的错误。你觉得输出会是什么呢?

练习:练习处理错误

如下练习提供对处理异步代码错误的练习,使用前一节中所描述的方法。要模拟异步操作,你的代码将调用如下已为你提供的函数:

函数 类型签名 描述
getNewUsername() Future<String> getNewUsername() 返回一个你可以用于替换旧名称的新用户名。

使用 async 和 await 来实现一个执行如下操作的异步函数 changeUsername()

  • 调用所提供的异步函数 getNewUsername() 并返回其结果。
    • changeUsername()中的示例返回值是: "jane_smith_92"
  • 捕获任意发生的错误并返回错误的字符串值。

练习:对所有内容进行练习

是时候在最终练习中练习所有已学知识了。为模拟异步操作,本练习提供了异步函数getUsername() 和 logoutUser()

函数 类型签名 描述
getUsername() Future<String> getUsername() 返回与当前用户关联的名称。
logoutUser() Future<String> logoutUser() 执行当前用户拿出并返回拿出的用户名。

编写如下内容:

Part 1: addHello()

  • 编写接收单个字符串变量的函数 addHello()
  • addHello() 返回由文本Hello <string>所包围的字符串参数。

Part 2: greetUser()

  • 编写一个不接收参数的函数 greetUser()
  • 为获取用户名,greetUser() 调用所提供的异步函数 getUsername()
  • greetUser() 通过调用 addHello()为用户创建一个问候语, 向其传递用户名,并返回结果。
    • 例如,如果用户是Jenny, greetUser() 应创建并返回如下内容:Hello Jenny

Part 3: sayGoodbye()

  • 编写执行如下操作的函数 sayGoodbye()
    • 不接收参数。
    • 捕获任意错误。
    • 调用所提供的异步函数 logoutUser()
  • 如果 logoutUser() 成功, sayGoodbye() 返回字符串<result> Thanks, see you next time,其中<result>是通过调用 logoutUser()返回的字符串值。

下一步进阶?

恭喜,你已完成了本codelab!如果想要学习更多,以下是一些探索更多课题的建议:

发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址