Dart 异步编程注意事项

作者: 岛上码农 | 来源:发表于2022-05-09 18:46 被阅读0次

    前言

    Dart 语言提供了多种异步编程方式,比如 Future,比如async / await,再比如 Stream。如何更好地进行异步编程,我们来看看官方的指引。

    相比 Future,优先使用 async / await

    异步代码的可读性差和难于调试是臭名昭著的,即便是使用 Future 这样比较好的抽象方式也是一样。 async / await 语法改善了可读性,并且可以在所有 Dart 的控制流的异步代码中使用。下面是一个典型的例子。通过 async / await 语法糖,代码逻辑非常清晰易懂。

    // 正确示例
    Future<int> countActivePlayers(String teamName) async {
      try {
        var team = await downloadTeam(teamName);
        if (team == null) return 0;
    
        var players = await team.roster;
        return players.where((player) => player.isActive).length;
      } catch (e) {
        log.error(e);
        return 0;
      }
    }
    

    如果使用原始的 Future 方式的话,那代码简直是无法直视 —— 一般人要理清这样的业务逻辑非常困难。

    // 错误示例
    Future<int> countActivePlayers(String teamName) {
      return downloadTeam(teamName).then((team) {
        if (team == null) return Future.value(0);
    
        return team.roster.then((players) {
          return players.where((player) => player.isActive).length;
        });
      }).catchError((e) {
        log.error(e);
        return 0;
      });
    }
    

    不要使用没必要的 async

    很容易习惯在有异步操作的函数定义时使用 async。但是,在某些情况下却是多余的。如果移除 async 对函数的行为没有影响的话,那么就大胆地移除吧。

    // 正确示例
    Future<int> fastestBranch(
        Future<int> left, Future<int> right) {
      return Future.any([left, right]);
    }
    
    // 错误示例
    Future<int> fastestBranch(Future<int> left, Future<int> right) async {
      return Future.any([left, right]);
    }
    

    下面是使用 async 的几个场景:

    • 在函数内部有使用到 await —— 这是最常见的情况。
    • 需要异步返回一个错误。使用 async 后再抛出错误会比使用 return Future.error(...) 更简洁。
    • 如果像用 Future 对象包裹一个返回值,那么使用 async 会比 Future.value(...) 更加简洁。
    // 正确示例
    Future<void> usesAwait(Future<String> later) async {
      print(await later);
    }
    
    Future<void> asyncError() async {
      throw 'Error!';
    }
    
    Future<void> asyncValue() async => 'value';
    

    避免直接使用 Completer

    很对异步编程的新手想写代码尝试产生一个 Future 对象,而 Future 的构造方法似乎满足不了他们的需要,然后他们就会使用 Completer 类来做这样的事情。

    // 错误示例
    Future<bool> fileContainsBear(String path) {
      var completer = Completer<bool>();
    
      File(path).readAsString().then((contents) {
        completer.complete(contents.contains('bear'));
      });
    
      return completer.future;
    }
    

    Completer适用于两类底层代码:新的异步原语和不使用 Future 对象的异步接口。大部分代码都应该使用 async / await 或者 Future.then,这是因为他们更加清晰,而且也更容易处理错误。比如下面的代码会更加清晰简洁,个人来说,会更喜欢 async /await的形式。

    // 正确示例1:使用 Future.then
    Future<bool> fileContainsBear(String path) {
      return File(path).readAsString().then((contents) {
        return contents.contains('bear');
      });
    }
    
    // 正确示例2:使用 async / await
    Future<bool> fileContainsBear(String path) async {
      var contents = await File(path).readAsString();
      return contents.contains('bear');
    }
    

    当处理 FutureOr<T>这种类型时,务必记得检查这个是Future<T>还是对象

    FutureOr<T> 声明的对象可能是 Future<T> 或仅仅是 T 类型的对象。因此,在处理这种类型时,通常需要使用 is 检查它到底是Future 对象还是普通对象。对于 FutureOr<int>这类特殊的类型来说,使用is intis Future<int>没什么区别。但是,如果值的类型是 Object或是一个使用 Object实例化的类型参数,那么就有区别了。这是因为如果使用 is T 判断的话,Future<T>对象因为也是 Object,结果会返回 true,比如下面的例子:

    // 正确示例
    Future<T> logValue<T>(FutureOr<T> value) async {
      if (value is Future<T>) {
        var result = await value;
        print(result);
        return result;
      } else {
        print(value);
        return value;
      }
    }
    
    // 错误示例
    Future<T> logValue<T>(FutureOr<T> value) async {
      if (value is T) {
        print(value);
        return value;
      } else {
        var result = await value;
        print(result);
        return result;
      }
    }
    

    错误示例中,如果传过去的参数是Future<Object>的话,那么logValue 这个函数会将它当做没有使用 Future 包裹的原始对象进行处理,结果导致程序错误。因此,对 FutureOr<T>是否是异步对象判断时,应当先使用 is Future<T>判断,次序不要弄错了。

    相关文章

      网友评论

        本文标题:Dart 异步编程注意事项

        本文链接:https://www.haomeiwen.com/subject/beahurtx.html