Dart17异步

异步模型

如何处理耗时的操作呢?针对如何处理耗时的操作,不同的语言有不同的处理方式。

  • 多线程,比如Java、C++,我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。

  • 单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理。不过单线程如何能进行耗时的操作呢?

阻塞式调用: 调用结果返回之前,当前线程会被挂起,调用线程只有在得到调用结果之后才会继续执行。

非阻塞式调用: 调用执行之后,当前线程不会停止执行,只需要过一段时间来检查一下有没有结果返回即可。

事件循环

单线程模型中主要是维护着一个事件循环(Event Loop),事实上事件循环并不复杂,它就是将需要处理的一系列事件(包括点击事件、IO事件、网络事件)放在一个事件队列(Event Queue)中,不断的从事件队列(Event Queue)中取出事件,并执行其对应需要执行的代码块,直到事件队列清空位置。

image-20240215151749321

齿轮就是我们的事件循环,它会从队列中一次取出事件来执行。

事件循环代码模拟:

这是一段 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中。