一、为什么要用异步?
异步操作让您的程序在等待另一个操作完成的同时完成工作。
下面是一些常见的异步方式。
- 通过网络获取数据。
- IO操作
要在Dart中执行异步操作,可以使用Future类和async和 await 关键字
何为Future呢?
future表示异步操作的结果,可以有两种状态:uncompleted or completed.
- uncompleted :当你调用异步函数时,当异步操作还没有完成时。
- completed:异步操作结束,将会返回异步操作结果,或者抛出一个异常。
常用API
Future.then
我们使用Future.delayed 创建了一个延时任务(实际场景会是一个真正的耗时任务,比如一次网络请求),即2秒后返回结果字符串"Hello World",然后我们在then中接收异步结果并打印结果,代码如下:
main() {
Future.delayed(Duration(seconds: 2), () {
return "Hello World";
}).then((result){
print(result);
});
}
Future.catchError
如果异步任务发生错误,我们可以在catchError中捕获错误.
main() {
Future.delayed(new Duration(seconds: 2), () {
//return "Hello World";
throw Exception("error");
}).then((data) {
print("success");
}).catchError((e) {
print(e);
});
}
出现异常后,then将不会回调, catchError回调函数将被调用。还有一种去捕获异常。
then方法还有一个可选参数onError,我们也可以它来捕获异常:
main() {
Future.delayed(new Duration(seconds: 2), () {
//return "Hello World";
throw Exception("error");
}).then((data) {
print("success");
}, onError: (e) {
print(e);
});
}
Future.whenComplete
无论异步任务执行成功或失败都需要做一些事的场景,比如在网络请求前弹出加载对话框,在请求结束后关闭对话框。
main() {
Future.delayed(new Duration(seconds: 2), () {
//return "Hello World";
throw Exception("error");
}).then((data) {
//执行成功会走到这里
print(data);
}).catchError((e) {
//执行失败会走到这里
print(e);
}).whenComplete(() {
//无论成功或失败都会走到这里
print("Complete");
});
}
Future.wait
有些时候,我们需要等待多个异步任务都执行结束后才进行一些操作,比如从两个接口获取数据,再将数据合并后,才能展示在界面上。类似RxJava的merge
main() {
Future.wait([
// 2秒后返回结果
Future.delayed(new Duration(seconds: 2), () {
return "hello";
}),
// 3秒后返回结果
Future.delayed(new Duration(seconds: 3), () {
return " world";
})
]).then((results) {
print(results[0] + results[1]);
}).catchError((e) {
print(e);
});
}
. 运行后,3秒后,将会打印 hello world
Future.wait,它接受一个Future数组参数,只有数组中所有Future都执行成功后,才会触发then的成功回调,只要有一个Future执行失败,就会触发错误回调,不会触发then回调。
Async/await
async和await关键字是Dart异步支持的一部分。他们允许你像写同步代码一样写异步代码和不需要使用Future接口。
Callback Hell(回调地狱)
了解 Async/await 之前,我们先解释何为Callback Hell(回调地狱)。
如果代码中有大量异步逻辑,并且出现大量异步任务依赖其它异步任务的结果时,必然会出现Future.then回调中套回调情况。
举个例子,比如现在有个需求场景是:
- 用户先登录,登录成功后会获得用户ID,
- 然后通过用户ID,再去请求用户个人信息,
- 获取到用户个人信息后,为了使用方便,我们需要将其缓存在本地文件系统。
首先我们先模拟创建出上面3个匿名函数来执行耗时操作,代码如下:
1:获取用户id
var login = <String>({String usename, String password}) {
return Future.delayed(Duration(seconds: 2), () {
if (usename == '123' && password == "123") {
print("获取用户id成功");
return "12";
} else {
throw Exception('账号密码错误');
}
});
};
2:获取用户信息
var getUserInfo = <String>(String id) {
return Future.delayed(Duration(seconds: 2), () {
print("$id 获取用户信息成功");
return "klaus";
});
};
3:保存用户信息
var saveUserInfo = (String userInfo) {
return Future.delayed(Duration(seconds: 2), () {
return '$userInfo 保存用户信息';
});
};
接下来我们按常规的方式去执行:
login(usename: '123', password: '123')
.then((id) => getUserInfo(id)
.then((userInfo) => saveUserInfo(userInfo))
.then((value) => print(value))
.catchError((e) {}))
.catchError((e) {
print(e);
});
从上面的代码可以感受到,如果业务逻辑中有大量异步依赖的情况,将会出现上面这种在回调里面套回调的情况,过多的嵌套会导致的代码可读性下降以及出错率提高,并且非常难维护,这个问题被形象的称为回调地狱(Callback Hell)。
接下来我们看看通过Future和async/await如何消除上面示例中的嵌套问题。
使用Future消除Callback Hell
login(usename: "123", password: "123").then((id) {
return getUserInfo(id);
}).then((userInfo) {
return saveUserInfo(userInfo);
}).then((e) {
//执行接下来的操作
print("保存用户信息success");
}).catchError((e) {
//错误处理
print(e);
});
Future 的所有API的返回值仍然是一个Future对象,所以可以很方便的进行链式调用,如果在then中返回的是一个Future的话,该future会执行,执行结束后会触发后面的then回调,这样依次向下,就避免了层层嵌套。这样看起来就清晰很多。
使用async/await消除callback hell
我们知道Kotlin的协程可以做到,像写同步代码一样去写异步代码,那Dart语言也有这种特性。这就要使用async/await了,可以像写同步代码那样来执行异步任务而不使用回调的方式。是不是很厉害。国际惯例,先上代码,再解释:
这里定义一个匿名函数,然后去执行它,
var task = ({String useName, String password}) async {
String id = await login(usename: useName, password: password);
String userInfo = await getUserInfo(id);
return saveUserInfo(userInfo);
};
task(useName: '123', password: '123').then((result) {
print('$result');
}).catchError((e) {
print('catchError $e');
});
- async用来表示函数是异步的,定义的函数会返回一个Future对象,可以使用then方法添加回调函数。
- await 后面是一个Future,表示等待该异步任务完成,异步完成后才会往下走;await必须出现在 async 函数内部。
- 若要定义异步函数,请在函数体之前添加 async 关键字。
- 如果在Future返回函数发生错误,你可能想捕获错误。Async函数可以用try-catch捕获错误。因为被async定义的函数 返回值是一个Future函数,所以也可以在catchError回调中获取异常。
- 你可以使用多个await表达式,保证一个await执行完成过后再执行下一个。
Dart中还有一种处理异步方式:Steam,下篇文章再来详细的讲解一下。