Dart17异步
Dart17异步
异步模型
如何处理耗时的操作呢?针对如何处理耗时的操作,不同的语言有不同的处理方式。
-
多线程,比如Java、C++,我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。
-
单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理。不过单线程如何能进行耗时的操作呢?
阻塞式调用: 调用结果返回之前,当前线程会被挂起,调用线程只有在得到调用结果之后才会继续执行。
非阻塞式调用: 调用执行之后,当前线程不会停止执行,只需要过一段时间来检查一下有没有结果返回即可。
事件循环
单线程模型中主要是维护着一个事件循环(Event Loop),事实上事件循环并不复杂,它就是将需要处理的一系列事件(包括点击事件、IO事件、网络事件)放在一个事件队列(Event Queue)中,不断的从事件队列(Event Queue)中取出事件,并执行其对应需要执行的代码块,直到事件队列清空位置。
齿轮就是我们的事件循环,它会从队列中一次取出事件来执行。
事件循环代码模拟:
这是一段 Flutter 代码,一个按钮 RaisedButton,当发生点击时执行 onPressed 函数。onPressed 函数中,我们发送了一个网络请求,请求成功后会执行then中的回调函数。
RaisedButton(
child: Text('Click me'),
onPressed: () {
final myFuture = http.get('https://example.com');
myFuture.then((response) {
if (response.statusCode == 200) {
print('Success!');
}
});
},
)
-
当用户发生点击的时候,onPressed回调函数被放入事件循环中执行,执行的过程中发送了一个网络请求。
-
网络请求发出去后,该事件循环不会被阻塞,而是发现要执行的onPressed函数已经结束,会将它丢弃掉。
-
网络请求成功后,会执行then中传入的回调函数,这也是一个事件,该事件被放入到事件循环中执行,执行完毕后,事件循环将其丢弃。
Future
- 同步的网络请求
使用getNetworkData来模拟了一个网络请求,该网络请求需要3秒钟的时间,之后返回数据,getNetworkData会阻塞main函数的执行。
main(List<String> args) {
print('开始请求');
String result = getNetworkData();
print('$result');
print('结束请求');
}
String getNetworkData() {
sleep(Duration(seconds: 3));
return "我是请求到的数据";
}
- 异步的网络请求
我们来对我们上面的代码进行改进,和刚才的代码唯一的区别在于我使用了Future对象来将耗时的操作放在了其中传入的函数中;代码如下:
main(List<String> args) {
print('开始请求');
var future = getNetworkData();
future.then((String value) {
print("结果:$value");
}).catchError((error) {
print('$error');
}).whenComplete(() {
print('代码执行完成');
});
print('结束请求');
}
// 模拟一个网络请求
Future<String> getNetworkData() {
return Future<String>(() {
// 将耗时操作包裹到 Future的函数回调中
// 1>、只要有返回结果,那么就执行 Future对应的回调(Promise-resolve)
// 2>、如果没有结果返回(有错误信息),需要在 Future 会调中跑出一个异常(Promise-reject)
sleep(Duration(seconds: 3));
throw Exception("我是错误信息");
// return "我是请求到的数据";
});
}
- Future 的链式调用
可以在 then 中继续返回值,会在下一个链式的 then 调用回调函数中拿到返回的结果,实例代码如下
main(List<String> args) {
print('start');
Future(() {
sleep(Duration(seconds: 3));
throw Exception("第一次异常");
// return '第1次网络请求的结果';
}).then((result) {
print('$result');
sleep(Duration(seconds: 3));
return '第2次网络请求的结果';
}).then((result) {
print('$result');
sleep(Duration(seconds: 3));
return '第3次网络请求的结果';
}).then((result) {
print('$result');
}).catchError((error){
print(error);
});
print('end');
}
- Future.delayed
在延迟一定时间时执行回调函数,执行完回调函数后会执行 then 的回调;
main(List<String> args) {
print("main function start");
Future.delayed(Duration(seconds: 3), () {
return "3秒后的信息";
}).then((value) {
print(value);
});
print("main function end");
}
async、await
async/await 只是一个语法糖,本质还是Future对象,await 会阻塞当前函数,直到完成。
main(List<String> args) async {
var future = await getNetworkData();
print(future);
}
// 模拟一个网络请求
Future<String> getNetworkData() {
return Future<String>(() {
sleep(Duration(seconds: 3));
return "我是请求到的数据";
});
}
-
await 关键字必须存在于 async 函数中。
-
使用 async 标记的函数,必须返回一个 Future 对象。
使用 await
修改下面代码:
main(List<String> args) {
getNetworkData().then((value) => print(value));
}
Future<String> getNetworkData() async {
var result = await Future.delayed(Duration(seconds: 3), () {
return "network data";
});
print("哈哈");
return "请求到的数据:$result";
}
我们现在可以像同步代码一样去使用 Future 异步返回的结果,等待拿到结果之后和其他数据进行拼接,然后一起返回;返回的时候并不需要包装一个Future,直接返回即可,但是返回值会默认被包装在一个Future中。