Dart【03】Future异步

123 阅读9分钟

什么是Future

Future是Dart处理异步的API。

异步方法

如果你的程序中有两个方法A 和B,方法A 在方法B之前执行,方法A执行需要五秒。

如果A方法是同步的,B方法会等A方法执行结束才会被调用,也就是说B方法五秒之后才会被执行。

如果A方法是异步的,程序在执行方法A时,发现A是一个异步方法,程序会将A放入待处理事件的末尾,接着执行B方法,也就是说B方法无须等待A方法执行完。

在客户端异步是非常有用的,如果你在初始化时有一个非常耗时,但又不需要它在ui画面响应前就执行完成的方法,你就可以使用异步。

下图展示了同步和异步的区别及Dart如何使用单线程处理异步。

如何创建Future方法

使用Future的构造方法

Future

Future构造函数的参数为Function(函数)类型,将你需要异步执行的代码写到该参数里。

Future(FutureOr<T> computation())

delayed

Future.delayed构造函数主要用于创建一个延迟一段时间执行的Future 。

该构造函数有两个参数:

duration参数来控制延迟多长时间 ;

Function类型的参数是需要异步执行的函数;

Future.delayed(Duration duration, [FutureOr<T> computation()])

microtask

Future.microtask构造函数是通过微任务队列处理的。

Future.microtask构造函数的参数为Function(函数)类型,将你需要异步执行的代码写到该参数里。

Future.microtask(FutureOr<T> computation())

PS:如果你不太理解微任务队列,这是Dart用来处理事件的队列之一,你可以在学习完本文后,了解一下作者的另一篇文章Dart进阶——一文学会Dart事件循环和异步调用

sync

立即返回结果的Future。

Future.sync构造函数的参数为Function(函数)类型,将你需要异步执行的代码写到该参数里。

Future.sync(FutureOr<T> computation())

四种构造方法的处理优先级

一段代码示例帮助你理解Future类四种构造方法的处理优先级。

Future(() => print('Future异步方法'));
​
Future.delayed(Duration(seconds: 3), () => print('delayed异步方法'));
​
Future.microtask(() => print('microtask异步方法'));
​
Future.sync(() => print('sync异步方法'));

控制台输出如下。

sync异步方法 microtask异步方法 Future异步方法 delayed异步方法

四种构造方法的处理优先级:

sync > microtask > Future = delayed

自定义返回值为Future的方法

创建一个方法,将方法的返回值声明为Future,将需要异步执行的代码写到方法返回的Future的构造函数里。

一定记住,只有写在Future构造函数里的代码才是异步的。

  Future testFuture() {
  //写在这里的方法是同步的
    return Future(() {//写在这里的代码是异步的});
  }

使用此方法的意义

你可能会有些疑问,我直接使用Future的构造方法就可以了,为什么还要这么写,这不是多此一举吗?

我会用两段代码给你介绍一下上述方式的作用。

使用Future构造方法创建的代码:

void main(List<String> arguments) {
  Future(() {
    for (var i = 0; i < 1000000000; i++) {}
    print('耗时任务结束');
  });
  print('main函数上的代码');
}

当你出于某些代码健壮性上的考虑,希望将你的异步代码从main函数中抽离出来,成为独立的方法的同时又能方便的进行Future的链式调用,上面提到的将方法返回值声明为Future的方式就有了用武之地。

将方法返回值声明为Future的方式创建的代码:

void main(List<String> arguments) {
  testFuture();
  print('main函数上的代码');
}
​
Future testFuture() {
  return Future(() {
    for (var i = 0; i < 1000000000; i++) {}
    print('耗时任务结束');
  });
}

如果你想指定Future构造函数的返回值类型,你可以这样写

这里泛型约束的值是调用then方法时的value

  Future<String> testFutureString() {
    return Future(() => "String类型数据");
  }

可以使用这种方式声明一个Future构造函数无返回值的Future方法

  Future<void> testFutureVoid() {
    return Future(() {});
  }

使用async关键字

async是Dart官方用于简化Future的关键字,我们使用async关键字实现和上面代码一样的功能。

Future<String> testFutureString() async {
  return "String类型数据";
}

Future类的常用方法

then

then方法用来注册Future完成时要调用的回调函数。

then方法的默认参数为Function,该方法的参数为Future的返回值。

then方法的另一个参数是onError,当调用then方法的Future出现异常时调用onErroronError方法的参数为dynamice, StackTrace stack

then方法的返回值为Future,可以很方便的进行链式调用。

代码示例:

Future(() {
  return 'future函数的返回值';
}).then((value) => print(value));

控制台输出如下

future函数的返回值

代码示例:

Future(() {
  throw StateError('This is a Dart exception in Future.');
}).then((value) => print(value), onError: (dynamic e, StackTrace stack) => print(e));

Bad state: This is a Dart exception in Future.

catchError

catchError用来处理调用它的Future出现的异常。

catchError方法的默认参数与thenonError参数一致,catchError方法的参数为dynamic e, StackTrace stack

catchError方法的另一个参数是test,返回值限定为bool类型的函数,该函数参数为异常对象,当test方法返回true时,catchError方法的默认参数被调用,当test方法返回false时,catchError方法的默认参数不会被调用。

catchError方法的返回值为Future,可以很方便的进行链式调用。

代码示例:

Future(() {
  throw StateError('This is a Dart exception in Future.');
}).catchError((dynamic e, StackTrace stack) => print(e), test: (e) => true);

控制台输出如下

Bad state: This is a Dart exception in Future.

wait

wait用来等待多个异步结果处理完成

代码示例:

  Future.wait([
    Future.delayed(Duration(seconds: 1), () {
      print("1");
    }),
    Future.delayed(Duration(seconds: 2), () {
      print("2");
    }),
  ]).then((value) {
    print(3);
  });

运行结果:在1S,2S,3S,会分别打印1,2,3

whenComplete

whenComplete用来注册一个在future完成时被调用的函数.

whenComplete的参数是一个没有返回值,没有参数的函数。当future完成时,函数会被调用,不管它是带着一个值还是一个错误。它类似于是一个异步的finally块。

whenCompletethen的区别为,当出现错误时then默认参数中的代码不会被调用,而whenComplete无论何时都会被调用。

whenComplete方法的返回值为Future,可以很方便的进行链式调用。

代码示例:

Future(() {
}).whenComplete(() => print(0));

timeout

timeoutFuture延迟指定指定时间后被调用。

timeout的第一个参数Duration timeLimit, 时间间隔。

第二个参数FutureOr<T> Function() onTimeout

future执行的时间超过Duration时,函数onTimeout就会被执行,值得注意的是onTimeout的返回值必须和调用它的Future返回值一致,onTimeout函数的返回值如果不是Future,会自动创建一个Future<T>类型。

timeout方法的返回值为Future,可以很方便的进行链式调用。

代码示例:

Future<bool>(() {
  return true;
}).timeout(Duration(milliseconds: 2), onTimeout: () {
  return false;
})

Flutter中的FutureBuilder

FutureBuilder组件可以监听Future函数的状态 ,根据Future函数的状态来返回不同的组件。

Future的状态

在Dart中异步有三种状态:

  • 未完成:第一种状态为未完成,异步方法正在执行当中,还没有返回结果,它的状态就为未完成。
  • 已完成并返回一个值:第二种状态为已完成并返回一个值,异步方法执行完成,并返回一个值。
  • 已完成并返回一个异常:第三种状态为已完成并返回一个异常,异步方法执行完成,并返回一个异常。

FutureBuilder组件的参数

FutureBuilder有三个主要参数:

  • future:future参数为FutureBuilder组件监听的异步函数。
  • initialData:当FutureBuilderfuture参数为空时,initialData的值会赋给snapshot.data
  • builder:builder参数为小部件构建函数,该函数有两个参数,contextsnapshot

builder函数的参数

builder函数有两个参数contextsnapshot:

  • context:上下文对象。

  • snapshot:我们重点来讲解一下snapshot参数,snapshot参数代表了异步的三种状态

    1.当异步方法已完成并返回一个值snapshot.hasData值为true

    2.当异步方法已完成并返回一个异常snapshot.hasError值为true

    3.当异步方法只在执行当中处于未完成的状态时,snapshot.connectionStateConnectionState.waiting

    4.当异步方法已完成snapshot.connectionStateConnectionState.done

    5.当FutureBuilderFuture参数为空时snapshot.connectionStateConnectionState.none

FutureBuilder组件的例子

当FutureBuilder监听的Future函数状态发生变化时,FutureBuilder会自动调用builder函数,组件重绘。我们可以根据snapshot参数来判断异步函数当前的状态,根据不同的状态返回不同的组件。

/// Copyright (C), 2020-2021, flutter_demo
/// FileName: futureBuilder_demo
/// Author: Jack
/// Date: 2021/2/4
/// Description:
import 'dart:async';
​
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
​
class FutureBuilderDemo extends StatefulWidget {
  @override
  _FutureBuilderDemoState createState() => _FutureBuilderDemoState();
}
​
class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
  Future<bool> fetchData() => Future.delayed(Duration(seconds: 1), () {
        debugPrint('Step 2, fetch data');
        return true;
      });
  Future fetchData2() => Future.delayed(Duration(seconds: 1), () {
    throw Exception();
   return Error();
  });
​
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: fetchData() ,
      initialData: false,
      builder: (context, snapshot) {
        if(snapshot.connectionState == ConnectionState.waiting){
          return Scaffold(
            body: Center(
                child: Container(child: Text('waiting: ${snapshot.data}'))),
          );
        }
        if(snapshot.connectionState == ConnectionState.none){
          return Scaffold(
            body: Center(
                child: Container(child: Text('none: ${snapshot.data}'))),
          );
        }
        if(snapshot.connectionState == ConnectionState.done){
          return Scaffold(
            body: Center(
                child: Container(child: Text('done: ${snapshot.data}'))),
          );
        }
        if(snapshot.hasError){
          return Scaffold(
            body: Center(
                child: Container(child: Text('hasError: ${snapshot.data}'))),
          );
        }
        if (snapshot.hasData) {
          debugPrint('Step 3, build widget: ${snapshot.data}');
          // Build the widget with data.
          return Scaffold(
            body: Center(
                child: Container(child: Text('hasData: ${snapshot.data}'))),
          );
        } else {
          // We can show the loading view until the data comes back.
          debugPrint('Step 1, build loading widget');
          return CircularProgressIndicator();
        }
      },
    );
  }
}
​

优化FutureBuilder的使用

每次重新构建FutureBuilder的父级时,异步任务都将重新启动,很多时候异步任务我们只需要在启动的时候调用一次就可以了。

优化思路

使用变量创建Future,不使用异步方法,异步方法会多次调用,异步变量只会在变量初始化时调用一次。

优化后的代码

/// Copyright (C), 2020-2021, flutter_demo
/// FileName: futureBuilder_demo
/// Author: Jack
/// Date: 2021/2/4
/// Description:
import 'dart:async';
​
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
​
class FutureBuilderDemo extends StatefulWidget {
  @override
  _FutureBuilderDemoState createState() => _FutureBuilderDemoState();
}
​
class _FutureBuilderDemoState extends State<FutureBuilderDemo> {
  Future<bool> _data;
​
  @override
  void initState() {
    super.initState();
    _data = fetchData();
  }
  Future<bool> fetchData() => Future.delayed(Duration(seconds: 1), () {
        debugPrint('Step 2, fetch data');
        return true;
      });
  Future fetchData2() => Future.delayed(Duration(seconds: 1), () {
    throw Exception();
   return Error();
  });
​
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: _data ,
      // future:fetchData(),
      initialData: false,
      builder: (context, snapshot) {
        if(snapshot.connectionState == ConnectionState.waiting){
          return Scaffold(
            body: Center(
                child: Container(child: Text('waiting: ${snapshot.data}'))),
          );
        }
        if(snapshot.connectionState == ConnectionState.none){
          return Scaffold(
            body: Center(
                child: Container(child: Text('none: ${snapshot.data}'))),
          );
        }
        if(snapshot.connectionState == ConnectionState.done){
          return Scaffold(
            body: Center(
                child: Container(child: Text('done: ${snapshot.data}'))),
          );
        }
        if(snapshot.hasError){
          return Scaffold(
            body: Center(
                child: Container(child: Text('hasError: ${snapshot.data}'))),
          );
        }
        if (snapshot.hasData) {
          debugPrint('Step 3, build widget: ${snapshot.data}');
          // Build the widget with data.
          return Scaffold(
            body: Center(
                child: Container(child: Text('hasData: ${snapshot.data}'))),
          );
        } else {
          // We can show the loading view until the data comes back.
          debugPrint('Step 1, build loading widget');
          return CircularProgressIndicator();
        }
      },
    );
  }
}
​