Flutter篇之你真的会使用Future吗?

最近用Flutter写了个简单的“爬虫”的东西,就是要不断地请求某些接口,而后读取数据就能够了。以前只是会简单地await和async来试用Future,后面发现只会简单的这种方式不行,因而去学习了Future的其余用法,仍是有点收获的,把Future的用法都学了一下,这里作个笔记。java

哈哈,别问我为何没用python去搞。。。恰好用的电脑没装环境而且python的API没那么熟,就有了个想法,反正都能实现,就用Flutter玩一下吧,可能后面还能够搞个可视化界面玩玩。python

Dart的消息循环机制

推荐文章:Dart与消息循环机制bash

这里简单说下Dart的事件循环,更加具体的话,能够看推荐的这篇文章。网络

相似Android的Handler/Looper机制,Dart也有相应的消息循环机制。dom

  • 一个Dart应用中有一个消息循环和两个队列,一个是event队列,一个是microtask队列。
  • 优先级问题:microtask队列的优先级是最高的,当全部microtask队列执行完以后才会从event队列中读取事件进行执行。
  • microtask队列中的event,
  • event队列包含全部的外来事件:I/O,mouse events,drawing events,timers,isolate之间的message等。
  • 通常Future建立的事件是属于event队列的(利用Future.microtask方法例外),因此建立一个Future后,会插入到event队列中,顺序执行。

Future的介绍

在写程序的过程当中,确定会有一部分比较耗时代码是须要异步执行的。好比网络操做,咱们须要异步去请求数据,而且还须要处理请求成功和请求失败的两种状况。异步

在Flutter中,使用Future来执行耗时操做,表示在将来会返回某个值,并可使用then()方法和catchError()来注册callback来监听Future的处理结果。async

以下面所示,源码中对Future的解释,以及简单的一个例子。then()方法和catchError()返回的其实也是一个Future类型对象,因此能够写成链式调用的形式。函数

An object representing a delayed computation.
Future<int> future = getFuture();
future.then((value) => handleValue(value))
      .catchError((error) => handleError(error));
复制代码

Flutter的状态

一个Future对象会有如下两种状态oop

  • pending:表示Future对象的计算过程仍在执行中,这个时候尚未能够用的result。
  • completed:表示Future对象已经计算结束了,可能会有两种状况,一种是正确的结果,一种是失败的结果。

建立Future

注意:首先Future是个泛型类,能够指定类型。若是没有指定相应类型的话,默认返回是Future类型的。学习

Future createFuture()async{
  return 1;
}
var future=createFuture();
print(future.runtimeType);。//输出结果为Future<dynamic>
复制代码

Future经常使用的建立有如下几种方式。

基本的用法

factory Future(FutureOr computation())

Future的构造方法,建立一个基本的Future

var future = Future(() {
  print("哈哈哈");
});
print("111");
//111
//哈哈哈
复制代码

你会发现,结果是打印“哈哈哈”字符串是在后面才输出的,缘由是默认future是异步执行的。若是改为下面的代码,在future前面加上await关键字,则会先打印“哈哈哈”字符串。await会等待直到future执行结束后,才继续执行后面的代码。

这个跟上面的事件循环是有关系的,转发大佬的解释:main方法中的普通代码都是同步执行的,因此确定是main打印先所有打印出来,等main方法结束后会开始检查microtask中是否有任务,如有则执行,执行完继续检查microtask,直到microtask列队为空。因此接着打印的应该是microtask的打印。最后会去执行event队列。

var future = await Future(() {
  print("哈哈哈");
});
print("111");
//哈哈哈
//111
复制代码

Future.value()

建立一个返回指定value值的Future

var future=Future.value(1);
print(future);
//Instance of 'Future<int>'
复制代码

Future.delayed()

建立一个延迟执行的future。 例以下面的例子,利用Future延迟两秒后能够打印出字符串。

var futureDelayed = Future.delayed(Duration(seconds: 2), () {
  print("Future.delayed");
  return 2;
});
复制代码

高级的用法

Future.foreach(Iterable elements, FutureOr action(T element))

根据某个集合,建立一系列的Future,而且会按顺序执行这些Future。 好比下面的例子,根据{1,2,3}建立3个延迟对应秒数的Future。执行结果为1秒后打印1,再过2秒打印2,再过3秒打印3,总时间为6秒。

Future.forEach({1,2,3}, (num){
  return Future.delayed(Duration(seconds: num),(){print(num);});
});
复制代码

Future.wait ( Iterable<Future> futures,{bool eagerError: false, void cleanUp(T successValue)})

用来等待多个future完成,并收集它们的结果。那这样的结果就有两种状况了:

  • 若是全部future都有正常结果返回:则future的返回结果是全部指定future的结果的集合
  • 若是其中一个future有error返回:则future的返回结果是第一个error的值。

好比下面的例子,也是建立3个延迟对应秒数的Future。结果是总时间过了3秒后,才输出[1, 2, 3]的结果。能够与上面的例子对比一下,一个是顺序执行多个Future,一个是异步执行多个Future。

var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);
var future2 =
    new Future.delayed(new Duration(seconds: 2), () => 2);
var future3 = new Future.delayed(new Duration(seconds: 3), () => 3);
Future.wait({future1,future2,future3}).then(print).catchError(print);
//运行结果: [1, 2, 3]
复制代码

将future2和future3改成有error抛出,则future.wait的结果是这个future2的error值。

var future1 = new Future.delayed(new Duration(seconds: 1), () => 1);
var future2 =new Future.delayed(new Duration(seconds: 2), () => throw “throw error2"); var future3 = new Future.delayed(new Duration(seconds: 3), () => throw “throw error3");
Future.wait({future1,future2,future3}).then(print).catchError(print);
//运行结果: throw error2
复制代码

Future.any(futures)

返回的是第一个执行完成的future的结果,不会管这个结果是正确的仍是error的。(感受这个的使用场景没几个的样子,想不到有什么场景要用到这个。)

例以下面的例子,使用Future.delayed()延迟建立了三个不一样的Future,第一个完成返回的是延迟1秒的那个future的结果。

Future
      .any([1, 2, 5].map(
          (delay) => new Future.delayed(new Duration(seconds: delay), () => delay)))
      .then(print)
      .catchError(print);
复制代码

Future.doWhile()

相似java中doWhile的用法,重复性地执行某一个动做,直到返回false或者Future,退出循环。

使用场景:适用于一些须要递归操做的场景。

好比我就是要爬某个平台的商品分类的数据,那每一个分类下面又有相应的子分类。那我就得拿对应父级分类的catId去请求接口,拿到这个分类下面的全部子分类。一样的,对全部的子分类,也要进行同样的操做,递归直到这个分类是一个叶子节点,也就是该节点下面没有子分类(有个leaf为true的字段)。哈哈,代码我就没贴了,其实也就是一个递归操做,直到知足条件退出future。

例以下面的例子,生成一个随机数进行等待,直到十秒以后,操做结束。

void futureDoWhile(){
  var random = new Random();
  var totalDelay = 0;
  Future
      .doWhile(() {
    if (totalDelay > 10) {
      print('total delay: $totalDelay seconds');
      return false;
    }
    var delay = random.nextInt(5) + 1;
    totalDelay += delay;
    return new Future.delayed(new Duration(seconds: delay), () {
      print('waited $delay seconds');
      return true;
    });
  })
      .then(print)
      .catchError(print);
}
//输出结果:
I/flutter (11113): waited 5 seconds
I/flutter (11113): waited 1 seconds
I/flutter (11113): waited 3 seconds
I/flutter (11113): waited 2 seconds
I/flutter (11113): total delay: 12 seconds
I/flutter (11113): null
复制代码

Future.microtask(FutureOr computation())

建立一个在microtask队列运行的future。

在上面讲过,microtask队列的优先级是比event队列高的,而通常future是在event队列执行的,因此Future.microtask建立的future会优先于其余future进行执行。

例以下面的代码,

Future((){
  print("Future event 1");
});
Future((){
  print("Future event 2");
});
Future.microtask((){
 print("microtask event");
});
//输出结果
//microtask event
//Future event 1
//Future event 2
复制代码

处理Future的结果

Flutter提供了下面三个方法,让咱们来注册回调,来监听处理Future的结果。

Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});
Future<T> catchError(Function onError, {bool test(Object error)});
Future<T> whenComplete(FutureOr action());
复制代码

Future.then()

用于在Future完成的时候添加回调。

注意:then()的返回值也是一个Future对象。因此咱们可使用链式的方法去使用Future,将前一个Future的输出结果做为后一个Future的输入,能够写成链式调用。

例以下面的代码,将前面的future结果做为后面Future的输入。

Future.value(1).then((value) {
  return Future.value(value + 2);
}).then((value) {
  return Future.value(value + 3);
}).then(print);
//打印结果为6
复制代码

Future.cathcError()

注册一个回调,来处理有error的Future

new Future.error('boom!').catchError(print);
复制代码

与then方法中的onError的区别:then方法里面也有个onError的参数,也能够用来处理错误的Future。

二者的区别,onError只能处理当前的错误Future,而不能处理其余有错误的Future。catchError能够捕获到Future链中抛出的全部错误。

因此一般的作法是使用catchError来捕捉Future中的全部错误,不建议使用then方法中的onError方法。否则每一个future的then方法都要加上onError回调的话,就比较麻烦了,并且代码看起来也是有点乱。

下面是两个捕捉错误的例子。

在那个抛出错误的future的then方法里添加onError回调的话,onError会优先被调用

Future.value(1).then((value) {
  return Future.value(value + 2);
}).then((value) {
  throw "error";
}).then(print, onError: (e) {
  print("onError find a error");
}).catchError((e) {
  print("catchError find a error");
});
//输出结果为onError find a error
复制代码

使用catchError来监听链式调用Future里面抛出来的错误。

Future.value(1).then((value) {
  throw "error";
}).then((value) {
  return Future.value(3);
}).then(print).then((value) {
}).catchError((e) {
  print("catchError find a error");
});
//输出结果为catchError find a error" 复制代码

Future.whenComplete

相似Java中的finally,Future.whenComplete老是在Future完成后调用,无论Future的结果是正确的仍是错误的。

注意:Future.whenComplete的返回值也是一个Future对象。

Future.delayed(Duration(seconds: 3),(){
  print("哈哈");
}).whenComplete((){
  print("complete");
});
//哈哈
//complete
复制代码

最经常使用的async和await

哈哈,上面说了一些Future的用法,其实有些可能并不会很经常使用,知道有这个用法就好了。

有个问题,若是有多个Future连接在一块儿的话,靠,代码可能会变得难以阅读。

因此,可使用async和await来使用Future,使代码看起来像是同步的代码,但实际上它们仍是异步执行的。

最经常使用的仍是async和await来使用Future。

注意:await只能在async函数出现。 async函数,返回值是一个Future对象,若是没有返回Future对象,会自动将返回值包装成Future对象。 捕捉错误,通常是使用try/catch机制来作异常处理。 await 一个future,能够拿到Future的结果,直到拿到结果,才执行下一步的代码。

好比下面的例子,建立一个延迟3秒并返回结果为1的Future,使用await来获取到future的值。

void main() async {
  var future = new Future.delayed(new Duration(seconds: 3), () {
    return 1;
  });
  var result = await future;
  print(result + 1);
}
 //输出为2
复制代码

下面的代码结构,跟java同样的写法,使用try-catch-finally来捕捉错误。

try {
  var result1 = await Future.value(1);
  print(result1);//输出1
  var result2 = await Future.value(2);
  print(result2);//输出2
} catch (e) {
    //捕捉错误
} finally {
  
}
复制代码

总结

  • 建立Future的几种方法
  • 用then、catchError、whenComplete来处理Future的结果
  • async和await的使用

下一步

多学习多整理。靠,又懒了好长一段时间了。。。

相关文章
相关标签/搜索