Alan Hou的个人博客

异步编程

异步编程: futures, async, await

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

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

这个codelab涵盖如下内容:

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

为什么异步代码很重要

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

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

示例:错误使用异步函数

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

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

在下一节中会学到有关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时记住这两个基本指南:

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

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

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

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

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

示例:同步函数

示例:异步函数

异步示例中有3处不同:

使用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() 函数中添加代码完成如下操作:

Part 2: reportLogins()

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

处理错误

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

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

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

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

练习:练习处理错误

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

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

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

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

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

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

编写如下内容:

Part 1: addHello()

Part 2: greetUser()

Part 3: sayGoodbye()

下一步进阶?

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

退出移动版