文章涵盖如下知识点:
- 如何以及什么时候使用
async 和await 关键字。 - 如何使用
async 和await 来影响执行顺序。 - 如何使用
try-catch 处理异步调用异常。
一、为什么异步代码很重要
异步操作允许程序在等待另一个操作完成时完成工作。以下是一些常见的异步操作:
要在Dart中执行异步操作,可以使用Future 类以及async 和await 关键字。
看一段有问题的代码:
Future<String> fetchUserOrder() =>
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
String createOrderMessage() {
var order = fetchUserOrder();
return 'Your order is: $order';
}
void main() {
print(createOrderMessage());
}
Your order is: Instance of 'Future<String>'
下面是示例无法打印fetchUserOrder()最终生成的值的原因:
- fetchUserOrder()是一个异步函数,它在延迟后提供一个描述用户订单的字符串:“Large Latte”。
- 要获取用户的订单,createOrderMessage()应该调用fetchUserOrder()并等待它完成。由于createOrderMessage()不等待fetchUserOrder()完成,createOrderMessage()无法获取fetchUserOrder()最终提供的字符串值。
- 相反,createOrderMessage()获取待完成的工作的表示:未完成的future。
- 由于createOrderMessage()无法获取描述用户订单的值,因此示例无法将“Large Latte”打印到控制台,而是打印“Your order is:Instance of ‘Future<String>’”。
Future.delayed()是一个工厂命名构造函数,函数原型如下: Future<T>.delayed(Duration duration, [FutureOr<T> computation()?]) 创建一个future,在延迟duration后,运行其可选参数函数computation。 1.computation将在给定的duration时间过后执行,future将随着computation结果的产生而完成。 2.如果computation返回future,则此构造函数返回的future将包含该future的值或错误。 3.如果duration时间为0或更小,则在所有微任务运行后,它将在下一个事件循环迭代中完成。 4.如果省略可选参数computaion,它将被视为computation为()=>null,future将最终以null值完成。在这种情况下,T必须可以为null。 5.如果调用computation抛出异常,则创建的future将以一个错误完成。 ——————————————————————————————— FutureOr<T> class: 表示Future<T>或T的值的一种类型。 此类声明是内部[future Or value]泛型类型的公共替代,该泛型类型不是类类型。对此类的引用解析为内部类型。 ——————————————————————————————— Duration class: A Duration represents a difference from one point in time to another. The duration may be “negative” if the difference is from a later time to an earlier. To create a new Duration object, use this class’s single constructor giving the appropriate arguments: var fastestMarathon = const Duration(hours: 2, minutes: 3, seconds: 2); The Duration is the sum of all individual parts. This means that individual parts can be larger than the next-bigger unit. For example, inMinutes can be greater than 59. assert(fastestMarathon.inMinutes == 123); var aLongWeekend = const Duration(hours: 88); assert(aLongWeekend.inDays == 3); 构造函数: const Duration({int days = 0, int hours = 0, int minutes = 0, int seconds = 0, int milliseconds = 0, int microseconds = 0})
Future.delayed()的具体底层实现代码如下:
factory Future.delayed(Duration duration, [FutureOr<T> computation()?]) {
if (computation == null && !typeAcceptsNull<T>()) {
throw ArgumentError.value(
null, "computation", "The type parameter is not nullable");
}
_Future<T> result = new _Future<T>();
new Timer(duration, () {
if (computation == null) {
result._complete(null as T);
} else {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
}
});
return result;
}
关键术语:
- 同步操作:同步操作阻止其他操作执行,直到完成。
- 同步函数:同步函数只执行同步操作。
- 异步操作:一旦启动,异步操作允许在完成之前执行其他操作。
- 异步函数:异步函数至少执行一个异步操作,也可以执行同步操作。
二、什么是future ?
一个future(小写“f”)是Future(大写“F”)类的一个实例。future表示异步操作的结果,可以有两种状态:未完成或已完成。
注:未完成(Uncompleted)是一个Dart术语,指future产生值之前的状态。
未完成(Uncompleted)和已完成(Completed)
未完成:当你调用异步函数时,它返回一个未完成的future。这个future将等待函数的异步操作完成或抛出错误。(上述有问题的示例代码中就是直接返回了一个未完成的future)
已完成:如果异步操作成功,则future将以值完成。否则,它将以错误完成。
以值完成:用值完成Future<T>类型的future将用T类型的值完成。例如,Future<String>类型的future将生成字符串值。如果future不产生可用值,则future的类型为future<void>。
以错误完成:如果函数执行的异步操作因任何原因失败,则future将以错误完成。
在下面的示例中,fetchUserOrder()返回一个future,这个future在打印控制台完成后完成。因为它不返回可用值,所以fetchUserOrder()的类型为Future<void>。
Future<void> fetchUserOrder() {
return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
}
void main() {
fetchUserOrder();
print('Fetching user order...');
}
Fetching user order...
Large Latte// 2秒后打印
一个future是怎样以一个错误完成的,如下代码:
Future<void> fetchUserOrder() {
return Future.delayed(const Duration(seconds: 2),
() => throw Exception('Logout failed: user ID is invalid'));
}
main() {
fetchUserOrder();
print('Fetching user order...');
}
Fetching user order...
Uncaught Error: Exception: Logout failed: user ID is invalid// 2秒后打印
快速回顾:
- Future<T>实例生成T类型的值。
- 如果future不产生可用值,则future的类型为Future<void>。
- future可以处于两种状态之一:未完成或已完成。
- 当你调用一个返回future的函数时,该函数会将要完成的工作排队,并返回一个未完成的future。
- 当future的操作完成时,future将以一个值或一个错误完成。
?
关键术语:
- Future:Dart的Future类。
- future:Future类的实例。
三、使用future :async 和await
在使用async 和await 时,记住两条基本指导:
- 使用异步函数,在函数体前加
async 关键字; await 关键字仅在异步函数中起作用。 如果你有一个async函数,你可以使用await关键字来等待一个future完成。
异步执行示例:
foo() async {
print('foo E');
String value = await bar();
print('foo X $value');
}
bar() async {
print("bar E");
return "hello";
}
main() {
print('main E');
foo();
print("main X");
}
绿框里面的代码会在foo函数被调用的时候同步执行,在遇到await的时候,会马上返回一个future,剩下的红框里面的代码以then的方式链入这个future被异步调度执行。
main E
foo E
bar E
main X
foo X hello
Process finished with exit code 0
可见print(‘foo X $value’)是在main执行完毕以后才打印出来的。的确是异步执行的。 而以上代码中的foo函数可以以Future方式实现如下,两者是等效的:
foo() {
print('foo E');
return Future(bar).then((value) => print('foo X $value'));
}
await并不像字面意义上程序运行到这里就停下来啥也不干等待future完成。而是立刻结束当前函数的执行并返回一个Future对象。函数内剩余代码通过调度异步执行。 await只能在async函数中出现。 async函数中可以出现多个await,每遇见一个就返回一个Future对象, 实际结果类似于用then串起来的回调。 async函数也可以没有await, 在函数体同步执行完毕以后返回一个Future对象。
示例1:同步函数
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>'// 不用等待,直接打印Uncompleted future
示例2:异步函数
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// 2秒后打印Completed future
Process finished with exit code 0
如果其他代码不变,将main()方法改为:
void main() {
print('Fetching user order...');
print(createOrderMessage());
}
则打印:
Fetching user order...// 不用等待,直接打印
Instance of 'Future<String>'// 注:没有前缀“Your order is: ”
Process finished with exit code 0// 2秒后打印
断点调试,发现调用顺序如下【0】~【8】:
Future<String> createOrderMessage() async {
var order = await fetchUserOrder();
return 'Your order is: $order';
}
Future<String> fetchUserOrder() =>
Future.delayed(
const Duration(seconds: 2),
() => 'Large Latte',
);
void main() {
print('Fetching user order...');
var orderMsg = createOrderMessage();
print(orderMsg);
}
为什么改main()为同步函数后的执行顺序是这样的??????
以上两个示例的不同点有3处:
- createOrderMessage()函数的返回类型从String变为Future<String>。
async 关键字出现在createOrderMessage()和main()方法体之前。await 关键字出现在调用异步函数fetchUserOrder()和createOrderMessage()方法之前。
关键术语: async :你可以在一个函数体前使用async关键字来标记函数为异步函数。 async function :一个异步函数是一个被async关键字标记的函数。 await :你可以使用await关键字来得到一个异步表达式的完成时结果。await关键字仅在async函数中起作用。
async 和await 的执行流程
一个异步函数同步执行,直到第一个await 关键字。
Future<void> printOrderMessage() async {
print('Awaiting user order...');
var order = await fetchUserOrder();
print('fetchUserOrder() completed!');
print('Your order is: $order');
}
Future<String> fetchUserOrder() {
return Future.delayed(const Duration(seconds: 4), () => 'Large Latte');
}
Future<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));
}
}
Awaiting user order...
1// 1秒后
2// 2秒后
3// 3秒后
4// 4秒后
fetchUserOrder() completed!
Your order is: Large Latte
四、处理异常
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;
}
Future<void> main() async {
await printOrderMessage();
}
Awaiting user order...
Caught error: Cannot locate user order// 4秒后
五、练习:将全部柔和在一起
String addHello(user) => 'Hello $user';
Future<String> greetUser() async {
var username = await fetchUsername();
return addHello(username);
}
Future<String> sayGoodbye() async {
try {
var result = await logoutUser();
return '$result Thanks, see you next time';
} catch (e) {
return 'Failed to logout user: $e';
}
}
六、更多
Stream tutorial:学习如何使用异步事件序列。
|