Dart异步编程之Future使用详解
Future.then
Future.delay延迟两秒后执行第二个参数里面的内容,返回一个Future对象,执行then后面的内容,then里面方法的参数为delayed第二个参数方法返回的内容,也就是"Hello World!"
Future.delayed(new Duration(seconds: 2),(){
return "Hello World!";
}).then((data){
print(data);
});
由于then方法返回还是一个Future,所以可以用链式调用一直拼接then执行相关代码
Future.delayed(new Duration(seconds: 2),(){
return 'Hello';
}).then((data){
return data + ' World';
}).then((data){
return data + ' and';
}).then((data){
return data + ' 野猿新一!';
}).then((data){
print(data);
}).catchError((e){
print(e);
});
运行结果如下
Hello World and 野猿新一!
Future.catchError
如下代码,在两秒后抛出一个AssertionError异常,程序往下走到catchError后面的的方法,输出异常信息,而没有走到then,因为只有程序正常执行才会走到then
Future.delayed(new Duration(seconds: 2),(){
// 2秒后抛出一个异常
throw AssertionError("Error");
}).then((data){
// 执行成功走到这里
print("success");
}).catchError((e){
// 执行失败走到这里
print(e);
});
onError
其实不止catchError方法可以捕获异常,在then方法中还提供了一个可选参数onError,当发生异常的时候,会走到onError参数所传入的方法
Future.delayed(new Duration(seconds: 2), () {
// 2秒后抛出一个异常
throw AssertionError("Error");
}).then((data) {
// 正常执行后回调该方法
print("success");
}, onError: (e) {
// 发生异常后回调该方法
print(e);
});
我们再来看下,若同时设置then方法的onError参数和调用catchError方法,当发生异常时程序会走到哪
Future.delayed(new Duration(seconds: 2), () {
throw AssertionError("Error");
}).then((data) {
print("success");
}, onError: (e) {
print('onError ' + e.toString());
}).catchError((e){
print('catchError ' + e.toString());
});
输出结果如下
onError Assertion failed
可以看到当同时设置onError和catchError的时候,当发生异常时,程序只会走onError,而不会走到catchError
当然实际写代码的也无需两个都写,这里我比较推荐写catchError,链式调用这样的代码层次比较清晰
Future.whenComplete
app开发中网络请求时经常在请求前先弹出一个加载对话框,当请求结束时不管成功失败都要关闭对话框。
这里有两种方法来关闭对话框,一种是在then和catchError(或者onError)中都写一段关闭的代码
另一种是通过在whenComplete里写一段关闭的代码,不管程序执行成功与否,都会走到whenComplete里面
程序正常运行的情况
Future.delayed(new Duration(seconds: 2),(){
return 'Hello';
}).then((data){
return data + ' World!';
}).then((data){
//执行成功走到这里
print(data);
}).catchError((e){
//执行失败走到这里
print(e);
}).whenComplete((){
//无论成功或失败都会走到这里
print('whenComplete');
});
输出结果如下
Hello World!
whenComplete
程序发生异常的情况
Future.delayed(new Duration(seconds: 2),(){
return 'Hello';
}).then((data){
throw AssertionError("Error");
}).then((data){
//执行成功走到这里
print(data);
}).catchError((e){
//执行失败走到这里
print(e);
}).whenComplete((){
//无论成功或失败都会走到这里
print('whenComplete');
});
输出结果如下
Assertion failed
whenComplete
可以看到,不管程序运行成功与否,最终都会走到whenComplete
Future.wait
假设我们有一个页面的Text控件要显示用户的昵称和等级,比如"野猿新一 等级30",但是获取用户昵称和等级是分属不同的接口,需要等到两个接口都获取成功后再把结果拼接在一起显示出来。
这里有两种实现方法,第一种方法是按顺序调用,先调用第一个接口,等第一个接口成功后再调用第二个接口,等第二个接口也成功后再拼接数据
第二种方法是同时调用两个接口,等两个接口都成功后(可能第一个接口先成功,也可能第二个接口先成功)再拼接数据
第一种方法可以用两个then来实现,如下所示
void main() {
run();
}
void run() {
print(DateTime.now());
Future.delayed(Duration(seconds: 2),(){
return '野猿新一';
}).then((data){
// 模拟获取等级耗时2秒
return Future.delayed(Duration(seconds: 2), (){
return data + " " + '等级30';
});
}).then((data){
print(data);
print(DateTime.now());
}).catchError((e){
print(e);
});
}
运行结果如下,耗时约4秒
2019-10-15 13:51:11.864
野猿新一 等级30
2019-10-15 13:51:15.868
第二种方法可以用Future.wait来实现
void main() {
run();
}
void run() {
print(DateTime.now());
Future.wait([
Future.delayed(new Duration(seconds: 2), () {
return '野猿新一';
}),
Future.delayed(new Duration(seconds: 2), () {
return '等级30';
})
]).then((results){
print(results[0] + ' ' + results[1]);
print(DateTime.now());
}).catchError((e){
print(e);
});
}
运行结果如下,耗时约2秒
2019-10-15 13:54:45.470
野猿新一 等级30
2019-10-15 13:54:47.477
既然以上两种方法都可以实现,那么该如何选择呢?
对于第一种通过两个then来实现的方法,适用于第二个接口对第一个接口的返回值有依赖的情况下,比如第二个接口请求用户等级需要传入第一个接口获取的用户昵称作为参数
如果两个接口无依赖关系,则建议用第二种方法Future.wait来实现,这种方法的优点是接口同时调用,耗时短。比如第一个接口耗时2秒,第二个接口耗时2秒,那么如果采用第一种方法耗时为2+2=4秒,若采用第二种方法,因为是同时开始执行,所以理想情况下是2秒。