Dart 笔记 12 - 异步

在 dart:async 库中,要先 import。

Future

delayed 创建一个延时任务。

Future.delayed(new Duration(seconds: 2),(){
   return "hi world!";
}).then((data){
   print(data);
});

String lookUpVersion() => '1.0.0';

如果将其改变成一个异步函数,返回值将是一个 Future,异步函数要有 async 关键字:

Future lookUpVersion() async => '1.0.0';

如果函数没有返回一个有用的值,那么返回 Future 类型。

设有三个异步函数

Future login(String userName, String pwd) async {
    //用户登录
    return '$userName-pwd'
}

Future getUserInfo(String id) async {
    //获取用户信息 
    return 'user id $id'
}

// 保存用户信息 
Future saveUserInfo(String userInfo) async => print(userInfo)
void main() {
  login("alice","1234").then((id) {
    // 登录成功后通过,id 指代返回的值   
    getUserInfo(id).then((userInfo) {
      // 获取用户信息后保存,无返回值时参数表示异常
      saveUserInfo(userInfo).then((e){
        // 保存用户信息,接下来执行其它操作
      });
    });
  });
}

结果输出 user id alice-pwd

链接多个异步函数

如果有大量的异步函数,像上面那样就会产生回调地狱。首先可以通过链式调用避免

void main() {
  login("alice","1234").then((id) {
    return getUserInfo(id);
  }).then((userInfo) => saveUserInfo(userInfo)
  ).then((e){
        
  }).catchError((e) { 
    // 使用 catchError() 来处理 Future 对象可能抛出的任何错误或异常
  });
}

then().catchError() 模式是 try-catch 的异步版本。

一定要在 then() 的结果上调用 catchError(),而不是在原始 Future 的结果上。否则,catchError() 只能从原始 Future 的计算中处理错误,而不能从 then() 注册的处理程序处理错误。

使用 await

还可以用 await 改造,使异步代码看起来像同步一样。

await 必须在 async 标注的异步函数中调用,返回一个 Future 对象。await 表达式会让程序执行挂起,直到返回的对象可用。

void main() async {
  try { // 可以捕获异常
    var id = await login("alice","1234");
    var userInfo = await getUserInfo(id);
    await saveUserInfo(userInfo);
  } catch (e) {

  } 
}

等待多个 Future

有时需要调用许多异步函数,并等待它们全部完成后再继续。使用 Future.wait() 静态方法管理多个 Future,并等待它们完成:

Future deleteLotsOfFiles() async =>  ...
Future copyLotsOfFiles() async =>  ...
Future checksumLotsOfOtherFiles() async =>  ...

await Future.wait([
  deleteLotsOfFiles(),
  copyLotsOfFiles(),
  checksumLotsOfOtherFiles(),
]);

print('Done with all the long steps!');

onError, whenComplete

then 有一个可选参数 onError,可以处理异常。

Future.delayed(new Duration(seconds: 2), () {
    //return "hi world!";
    throw AssertionError("Error");
}).then((data) {
    print("success");
}, onError: (e) {
    print(e);
});

无论异步任务执行成功或失败都要做的事,在 Future 的 whenComplete 回调里执行

Future.delayed(new Duration(seconds: 2),(){
   throw AssertionError("Error");
}).then((data){
   // 执行成功会走到这里 
   print(data);
}).catchError((e){
   // 执行失败会走到这里   
   print(e);
}).whenComplete((){
   // 无论成功或失败都会走到这里
});

Stream

也是用于接收异步事件数据,和 Future 不同的是,它可以接收多个异步操作的结果(成功或失败)。也就是说,在执行异步任务时,可以通过多次触发成功或失败事件来传递结果数据或错误异常。 Stream 常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。

Stream.fromFutures([
  // 1秒后返回结果
  Future.delayed(new Duration(seconds: 1), () {
    return "hello 1";
  }),
  // 抛出一个异常
  Future.delayed(new Duration(seconds: 2),(){
    throw AssertionError("Error");
  }),
  // 3秒后返回结果
  Future.delayed(new Duration(seconds: 3), () {
    return "hello 3";
  })
]).listen((data){
   print(data);
}, onError: (e){
   print(e.message);
},onDone: (){

});

结果

I/flutter (17666): hello 1
I/flutter (17666): Error
I/flutter (17666): hello 3

使用异步 for 循环

有时可以使用异步 for 循环(wait for),而不是使用流 API。

下面的函数使用 Stream 的 listen() 方法订阅一个文件列表,传入一个搜索每个文件或目录的函数文字。

void main(List arguments) {
  // ...
  FileSystemEntity.isDirectory(searchPath).then((isDir) {
    if (isDir) {
      final startingDir = Directory(searchPath);
      startingDir
          .list(
              recursive: argResults[recursive],
              followLinks: argResults[followLinks])
          .listen((entity) {
        if (entity is File) {
          searchFile(entity, searchTerms);
        }
      });
    } else {
      searchFile(File(searchPath), searchTerms);
    }
  });
}

带有 await 表达式的等价代码,包括异步 for 循环(await for),看起来更像同步代码:

Future main(List arguments) async {
  // ...
  if (await FileSystemEntity.isDirectory(searchPath)) {
    final startingDir = Directory(searchPath);
    await for (var entity in startingDir.list(
        recursive: argResults[recursive],
        followLinks: argResults[followLinks])) {
      if (entity is File) {
        searchFile(entity, searchTerms);
      }
    }
  } else {
    searchFile(File(searchPath), searchTerms);
  }
}

监听流数据

要在每个值到达时获得它,可以使用 await() 方法对流使用或使用 listen() 方法订阅:

// Find a button by ID and add an event handler.
querySelector('#submitInfo').onClick.listen((e) {
  // When the button is clicked, it runs this code.
  submitData();
});

onClick 属性是 “submitInfo” 按钮提供的流对象。

如果只关心一个事件,那么可以使用属性 first、last 或 single 来获得它。要在处理事件之前测试它,可以使用诸如 firstWhere()、lastWhere() 或 singleWhere() 之类的方法。

如果关心事件的子集,可以使用诸如 skip()、skipWhile()、take()、takeWhile() 和 where() 等方法。

改变流数据

通常需要在使用流数据之前更改其格式。使用 transform() 方法生成具有不同类型数据的流:

var lines = inputStream
    .transform(utf8.decoder)
    .transform(LineSplitter());

首先使用 utf8.decoder 将整数流转换为字符串流。然后使用 LineSplitter 将字符串流转换为单独的行流。这些转换器来自 dart:convert 库。

处理错误和完成

如何指定错误和完成处理代码取决于是使用异步 for 循环(wait for)还是流 API。

如果使用异步 for 循环,则使用 try-catch 处理错误。在流关闭后执行的代码在异步 for 循环之后执行。

Future readFileAwaitFor() async {
  var config = File('config.txt');
  Stream> inputStream = config.openRead();

  var lines = inputStream
      .transform(utf8.decoder)
      .transform(LineSplitter());
  try {
    await for (var line in lines) {
      print('Got ${line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
    print(e);
  }
}

如果使用流 API,则通过注册 onError 侦听器来处理错误。通过注册 onDone 侦听器,在流关闭后运行代码。

var config = File('config.txt');
Stream> inputStream = config.openRead();

inputStream
    .transform(utf8.decoder)
    .transform(LineSplitter())
    .listen((String line) {
  print('Got ${line.length} characters from stream');
}, onDone: () {
  print('file is now closed');
}, onError: (e) {
  print(e);
});

可以使用 break 或 return 语句,该语句将跳出 for 循环,并从流中取消订阅。

你可能感兴趣的:(Dart 笔记 12 - 异步)