前言
上一篇文章《【Flutter基础】Dart中的并发Isolate》中,咱们介绍了 Isolate 的相关知识点。今日咱们就回过头来看一看 Future,Future 能够说是咱们在项目中运用最多的异步调用对象,但咱们真的完全了解 Future 吗?看完这篇文章,我想你能够找到答案~
项目源码地址 Github
一、Future的根本用法
1.1 Future(FutureOr computation())
这儿有一个计数器的例子,经过 Future(FutureOr<T> computation())
来完结异步计数功用,代码如下:
/// 1.1 Future(FutureOr<T> computation())
void futureTest() {
int _count = 0;
print('1.开端count=$_count');
Future(() {
for (int i = 0; i < 10000000; i++) {
_count++;
}
print('2.核算完结count=$_count');
});
print('3.完毕count=$_count');
}
运转成果如下:
能够看到,Future 中的履行函数,会在终究履行打印,且不会堵塞后续的代码履行。然后导致打印成果为:1-3-2,而不是 1-2-3。
1.2 Future.value()
Future.value()
办法十分简略,直接回来一个 Future 的回来值:
/// 1.2 Future.value
void futureValueTest() {
Future<int>.value(2021).then((value) {
print('value: $value');
});
}
运转成果:
1.3 Future.then
为了处理上述打印次序的问题,这儿咱们运用 Future.then
办法来完结代码的次序加载。代码如下:
/// 1.3 Future.then
void futureThenTest() {
int _count = 0;
print('1.开端count=$_count');
Future(() {
for (int i = 0; i < 10000000; i++) {
_count++;
}
print('2.核算完结count=$_count');
}).then((value) => print('3.完毕count=$_count'));
}
履行成果:
能够看到此刻的打印次序为 1-2-3 并且 3 获取到的值的确是核算后的值,这样就完结了异步调用的同步操作。
1.4 Future.delayed
经过 Future.delayed
咱们能够完结延时调用功用,代码如下:
/// 1.4 Future.delayed
void futureDelayedTest() {
print('1.开端履行: ${DateTime.now()}');
Future.delayed(const Duration(seconds: 2), () {
print('2.延时2秒履行: ${DateTime.now()}');
});
print('3.完毕履行: ${DateTime.now()}');
}
咱们经过 delayed
延迟2秒履行核算,运转成果:
能够看到的确延迟了 2秒 履行核算使命。
1.5 await-async
除了运用 Future.then
办法来完结同步功用外,咱们还能够运用 await-async
关键字来完结异步调用的同步功用。代码如下:
/// 1.5 async 和 await
Future<void> awaitAsyncTest() async {
int _count = 0;
print('1.开端count=$_count');
await Future(() {
for (int i = 0; i < 10000000; i++) {
_count++;
}
print('2.核算完结count=$_count');
});
print('3.完毕count=$_count');
}
运转成果:
不知道咱们发现了没有,对比 futureValueTest()
和 awaitAsyncTest()
办法的运用,咱们仅仅增加了 await-async
关键字就完结了异步转同步的操作,无需运用函数回调等嵌套回调处理,这让咱们写的代码愈加清晰明确,除此之外在某些其他方面 await-async
也是更有优势。
Future.then
与 await-async
调用的差异在于,Future.then
无需给办法增加 async
关键字,缺陷在于回来值会嵌套到 Future.then
中,导致办法获取回来值愈加费事。这儿推荐运用 await-async
愈加直观。
二、Future的高级用法
2.1 反常处理
2.1.1 catchError
咱们能够经过 catchError
来捕获函数调用中抛出的反常,这儿咱们经过 throw Exception()
手动抛出错误。代码如下:
/// 2.1.1 catchError
void catchErrorTest() {
int _count = 0;
print('1.开端count=$_count');
Future(() {
_count++;
throw Exception('核算错误');
print('2.核算完结count=$_count');
})
.then((value) => print('3.核算完结count=$_count'))
.catchError((error) => print('4.捕获反常 : $error'))
.then((value) => print('5.核算完结count=$_count'));
}
运转成果:
剖析输出成果,咱们能够发现以下几点定论:
-
throw Exception
反常抛出时,在捕获前,后边的代码都不会再履行,所以 2、3 处没有打印。 - 反常捕获后,能够经过
catchError
获取反常 error 信息,履行反常处理。 - 反常捕获后,后续代码依然能够履行。
2.1.2 onError
除了 catchError
能够捕获反常外,咱们还能够经过 Future.then
的 onError
办法捕获反常。代码如下:
/// 2.1.2 onError
void onErrorTest() {
int _count = 0;
print('1.开端count=$_count');
Future(() {
_count++;
throw Exception('核算错误1');
}).then((value) {
print('2.核算完结count=$_count');
}, onError: (error) {
print('3.捕获反常 : $error');
});
}
运转成果:
能够看到,当抛出反常时,Future.then
的 onValue
函数不会履行,而是在 onError
函数中捕获反常。
2.1.3 catchError 与 onError 的差异
为了对比 catchError 与 onError的差异,咱们这儿有一个 onError 的案例:
/// 2.1.3 catchError 与 onError 的差异
void futureErrorTest1() {
int _count = 0;
print('1.开端count=$_count');
Future(() {
_count++;
throw Exception('核算错误1'); // tag1
}).then((value) {
}, onError: (error) {
print('2.捕获反常 : $error'); // tag2
// throw Exception('核算错误2');
throw error;
}).then((value) => null, onError: (error) {
print('3.捕获反常 : $error'); // tag3
});
}
咱们在 tag1
处抛出一个反常,在 tag2
处理进行捕获并继续抛出该反常,终究在 tag3
处理继续捕获反常。
运转成果:
能够看到,onError
不仅能够捕获事情源抛出的反常,也能够捕获后续履行函数抛出的反常。
咱们再来看一下 catchError
的调用,代码如下:
/// 2.1.3 catchError 与 onError
void futureErrorTest2() {
int _count = 0;
print('1.开端count=$_count');
Future(() {
_count++;
throw Exception('核算错误1');
}).catchError((error) {
print('2.捕获反常 : $error');
throw Exception('核算错误2');
// throw error;
}).catchError((error) {
print('4.捕获反常 : $error');
});
}
运转成果:
能够看到,虽然 catchError
也能够正常捕获到反常,但是终究会抛出一个 Unhandled exception 的反常
出去。剖析抛出的反常原因,是由于 catchError
回调必须回来一个 Future's
类型导致的。但是咱们的Future自身没有回来值,那么该如何处理呢?
处理方法:
-
主张运用
async-await
和try-catch
而不是运用.catchError
,这样能够避免这种混淆(咱们上面介绍其他方面的优势在这儿也能够体现)。 -
假如一定要运用
catchError
能够在事情源上增加一个then
函数回调,此刻会将本来的回来值由Future<Never>
类型转化成Future<Null>
类型,然后能够为catchError
提供一个Future
类型的回来值,如此即可避免该问题产生。// 2.1.3 catchError 与 onError Future<void> futureErrorTest3() async { int _count = 0; print('1.开端count=$_count'); Future(() { _count++; throw Exception('核算错误1'); }).then((value) { }).catchError((error) { print('2.捕获反常 : $error'); }); }
运转成果:
2.1.4 Future.error
除了上述经过 throw Exception
抛出反常外,咱们还是经过 Future.error
来抛出一个反常。代码如下:
/// 2.1.4 Future.error
futureErrorTest(){
int _count = 0;
print('1.开端count=$_count');
Future(() {
_count++;
return Future.error(Exception('核算错误1'));
}).catchError((error) {
print('2.捕获反常 : $error');
});
}
运转成果:
这儿咱们发现经过
Future.error
抛出的反常,并不会导致catchError
报错。因而关于catchError
的处理还有一种方法,那就是将Exception
经过Future.error
来转化。
2.2 Future.whenComplete
在 Future 中 Future.whenComplete
表明在整个 Future 链路履行完结后终究的调用。即使在 Future 链路调用中产生了反常,该办法也一定会履行。一般咱们用来做一些 IO操作 的完毕处理,比方:读写文件流的 close、数据库cursor 的关闭等。
/// 2.2 Future.whenComplete
void whenCompleteTest() {
Future(() {
throw Exception('核算错误1');
}).then((value){
}).catchError((error){
print('捕获反常: $error');
throw error;
}).whenComplete(() {
print('whenComplete');
});
}
运转成果:
2.3 Future.wait
不知道咱们在作业中有没有遇到过这样的需求,某一个页面需求 2个接口的数据来拼接显示。此刻,一般咱们想到的是采用 await-async
来完结两个接口的数据同步处理,但这并不是最优解,由于两个接口的恳求是串行的。咱们希望的是两个接口能够一起发起恳求,当两个接口恳求都完结时即可进行回调。此刻 Future.wait
就排上了用场。
Future.wait
表明一起履行多个异步使命,在所有使命履行完结,或者产生反常时进行回调。
// 2.3 Future.wait
void futureTest11() {
Future.wait([
Future(() => print('使命1')),
Future(() => print('使命2')),
Future(() => print('使命3')),
]).then((value) => print('完结所有使命'));
}
运转成果:
2.4 Future.timeout
Future 中 Future.timeout
办法表明给异步使命设置超时时长并在使命超时后进行回调处理。
/// 2.4 Future.timeout
void futureTest12() {
Future(() {
return Future.delayed(const Duration(seconds: 3), () => print('1.完结使命'));
}).timeout(const Duration(seconds: 2), onTimeout: () {
print('2.使命超时');
}).then((value) {
print('3.完毕使命');
});
}
运转成果:
2.5 Future.doWhile
Future.doWhile
望文生义在 Future中履行 do-while
处理。咱们也能够直接在 Future 里面履行 do-while
循环,这儿只不过封装了一层而已。代码如下:
/// 2.5 Future.doWhile
void doWhileTest() {
int _count = 0;
Future.doWhile(() async {
_count++;
await Future.delayed(const Duration(seconds: 1));
if (_count == 3) {
print('Finished with $_count');
return false;
}
return true;
});
}
运转成果:
2.6 Future.forEach
Future.forEach
也和咱们日常用到的 forEach
一样,遍历每个item处理,遍历完毕后,回来履行成果。
这儿咱们看一下 Future.forEach
源码,发现其内部是经过 Future.doWhile
来完结迭代器的遍历。
static Future<void> forEach<T>(
Iterable<T> elements, FutureOr action(T element)) {
var iterator = elements.iterator;
return doWhile(() {
if (!iterator.moveNext()) return false;
var result = action(iterator.current);
if (result is Future) return result.then(_kTrue);
return true;
});
}
2.7 Future.microtask
Future.microtask
表明将一个 Future 增加到微使命行列中履行,履行完毕后回调到Future中。关于微使命行列咱们或许比较陌生,更好奇为什么要这样处理,关于这些咱们先按下不讲。后边介绍 Dart事情循环和行列 时咱们在了解。源码:
factory Future.microtask(FutureOr<T> computation()) {
_Future<T> result = new _Future<T>();
scheduleMicrotask(() {
try {
result._complete(computation());
} catch (e, s) {
_completeWithErrorCallback(result, e, s);
}
});
return result;
}
2.8 Future.sync
Future.sync
望文生义即Future同步的功用。下面是其完结源码:
factory Future.sync(FutureOr<T> computation()) {
var result = computation();
if (result is Future<T>) {
return result;
} else {
// TODO(40014): Remove cast when type promotion works.
return new _Future<T>.value(result as dynamic);
}
}
能够看到先履行 computation()
函数调用,若函数回来值也是 Future 则直接回来,不然经过 _Future.value() 进行包装。
看到这儿咱们或许有点懵逼,但 new _Future.value 和 Future.value
有啥差异?
这儿有一个用例,能够让咱们认知 Future.sync
与 Future.value
的差异。
/// 2.6 Future.sync
Future<void> syncTest() async {
Future future1 = Future.value(1); // tag1
Future future2 = Future<int>(() => 2); // tag5
Future future3 = Future.value(Future(() => 3)); // tag6
Future future4 = Future.sync(() => 4); // tag4
Future future5 = Future.sync(() => Future.value(5)); // tag2
scheduleMicrotask(() => print(6)); // tag3
future1.then(print);
future2.then(print);
future3.then(print);
future4.then(print);
future5.then(print);
}
输出成果:
剖析上述成果:
-
Future.vaue
的优先级是最高的,不管Future.value
放在何处位置,都会优先履行完,由于它是同步的。 -
Future.sync
关于computation
履行成果为Future<T>
的会直接回来,等价于履行Future.vaue(4)
,所以tag2
是第二个打印。 -
tag3
处是将一个微使命增加到微使命行列中,在当前使命履行完毕后,会优先进入到微使命行列的履行,所以tag3
处第三个打印。 -
Future.aync
关于computation
成果为非 Future 类型的值,进行_Future<T>.value
包装。其优先级在事情行列之前、微使命行列之后,因而tag4
会打印。 - 终究履行事情行列中的使命,
tag5
和tag6
会按增加次序打印。
三、Dart事情循环和行列
3.1 Dart事情循环和行列
Dart 应用程序有一个带有两个行列的事情循环*——* 事情行列和微使命行列。
如下图所示,当main()履行时,事情循环开端作业。首要,它以 先进先出 次序履行任何微使命。然后它出列并处理事情行列中的第一项。然后重复这个循环:履行所有微使命,然后处理事情行列中的下一个项目。
一般咱们的代码都是在事情行列里面运转。这也是为什么代码履行出错后,程序缺没有崩溃,依然能够履行其他代码的原因。
如何增加使命:
- Future类,将一个使命增加到事情行列的结尾。
- scheduleMicrotask()函数,将一个使命增加到微使命行列的结尾。
当咱们运用Future()或
Future.delayed()时,即在事情行列结尾增加了一个新的使命。但也仅仅增加了一个使命,并不会立即履行,而是会等事情行列里的使命悉数履行完结后,才会履行。这也是 [1.1末节](#1.1 Future(FutureOr computation()) 中履行成果不是按次序1-2-3 打印的原因。
3.2 Future履行次序
了解到了Dart的事情循环和行列机制,咱们来测验下 Future 使命的履行次序来帮助咱们加深理解。
这儿依照从上到下有1-9个编号打印,请咱们思考下打印成果是怎样的次序:
/// 3.2 event loop
void futureEventLoopTest() {
Future future1 = Future(() {
print('使命1');
});
future1.then((value) {
print('使命2');
scheduleMicrotask(() => print('使命3'));
}).then((value) {
print('使命4');
});
Future future2 = Future(() => print('使命5'));
Future(() => print('使命6'));
scheduleMicrotask(() => print('使命7'));
future2.then((value) => print('使命8'));
print('使命9');
}
咱们来剖析下上述的使命履行进程:
首要,咱们能够剖分出前面的代码一直是在事情行列和微使命行列增加使命,所以终究一行 使命9
的打印。
当前的使命现已履行完结,开端进入到微使命行列履行,而使命7
所在的使命是微使命,开端履行打印。
然后履行 future1,此刻按次序打印使命1
、使命2
、使命4
并把一个使命增加到微使命行列,履行完毕。
然后,进入到微使命行列,履行使命3
的打印。
再进入到 future2 的履行,打印使命5
、使命8
,履行完毕。
终究,一个 Future 履行,打印使命6
。
所以履行成果为:9-7-1-2-4-3-5-8-6
结语
至此,咱们完结了对 Future 概念和用法的剖析,不知道咱们是否悉数掌握呢?能够评论区留言。
假如觉得这篇文章对你有所帮助的话,不要忘记一键三连哦,咱们的点赞是我更新的动力。
项目源码:Github
参考资料:
Dart官网
事情循环和行列