Flutter笔记--Dart基础语法

Dart中一切皆对象

Dart内置类型

Numbers

Numbers类型包含两种:
int :int类型不超过64位,根据平台的不同,取值范围不同。在Dart虚拟机中int取值范围为-2632 63,编译成JavaScript的Dart使用 JavaScript numbers,允许从-2 53253 - 1的值
double :64位(双精度)浮点数,由IEEE 754标准指定

  //int 和 double 都是num的子类型
  num n = 10;
  num n1 = 20.0;
  
  int i = n;
  double d = n1;

  print(i.runtimeType); //int
  print(d.runtimeType); //double
  print(n.runtimeType); //int
  print(n1.runtimeType); //double

 //在Dart2.1,必要的话,整型会自动转换为双精度
  double z = 1;  //相当于double z = 1.0, 在Dart2.1之前这种写法是会报错的
String

字符串创建方式:‘单引号创建’“双引号创建”'''三个单引号(有格式的)'''"""三个双引号(有格式的)"""以r为前缀的原始字符串

//单引号和双引号比较常用
String s = 'single quotes';
String d = "double quotes";

//三个单引号和三个双引号是一样的
String t = '''three single quotes''';
String t1 = """three double quotes""";

//三个单、双引号字符串一般用于创建多行字符串,即
  String t2= '''$t
         can be styled virtually
 any way you want ''';

//输出结果即包含输入的样式
  print(t2);
  /**

      three single quotes
      can be styled virtually
      any way you want
   */
//在单双引号或三单引号以及三双引号中$、\n等特殊字符还是会特殊处理的,而对于以r为前缀的原始字符串来说,任何字符都不会得到区别对待
  int i = 10;
  var s = r'In a raw string,$i not even \n gets special treatment.';

  var s1 = 'In a raw string, $i not even \n gets special treatment.';
  print(s);
  print(s1);
    /*
      In a raw string,$i not even \n gets special treatment.
      In a raw string, 10 not even
      gets special treatment.
     */

//其中 ${表达式} 可以获取到该表达式的值,另外该表达式本身是一个变量时可省略{}

另外再说一下dart中字符串的拼接,除了+操作符拼接还可直接排放到一起

 String jointStr1 = 'str1'+'str2'+'str3';
 String jointStr2 = 'str1''str2''str3';
 print('$jointStr1 \n$jointStr2');
  /*
  str1str2str3 
  str1str2str3
  */ 
Boolean

Dart使用bool表示布尔值,只有两个对象具有bool类型,truefalse,都是编译时常量

 String jointStr1 = 'str1'+'str2'+'str3';
 String jointStr2 = 'str1''str2''str3';
 bool a = jointStr1 == jointStr2;
  if (a) {
    print('==操作符可用于判断两个对象是否相等,当两个字符串包含的字符一致时,这两个字符串是相等的');
  }

Lists

在Dart中,Array也是List,所以统称为Lists(列表).与其它编程语言类似,列表使用基于0的索引,其中0是第一个元素和列表的索引。length - 1是最后一个元素的索引

 //声明一个List
  List list = [1,2,3];

  //获取list中的元素
  print(list[0]); //1
  print(list.first);//1
  print(list.length); //3
  print(list[list.length-1]); //3
  print(list.last); //3

  //添加和删除元素
  list.add(4);
  print(list); //[1, 2, 3, 4]
  list.remove(1);
  print(list); //[2, 3, 4]
  list.removeAt(0);
  print(list); //[3, 4]
  list.insert(0, 1);
  print(list); //[1, 3, 4]
  list.insert(1, 2);
  print(list); //[1, 2, 3, 4]

  //需要注意list的insert(int index,E element)方法中的index取值范围为0..${list.length}
  print(list.length); //4
  list.insert(4, 5);
  print(list); //[1, 2, 3, 4, 5]
  // list.insert(6, 6); // throw RangeError: Invalid value: Not in range 0..5, inclusive: 6

Maps

(Map)映射,是一个将键和值(keys-values)关联起来的对象。key和value都可以是任何类型的对象,在一个map中,每个key只能出现一次,但是多个key,可以对应同样的value

//定义一个Map
Map<int,String> map = {
  //key : value
     1  : 'a',
     2  : 'b',
     3  : 'c',
};

//添加一个新的key:value
map[4] = 'b';

//根据key获取value
print('${map[1]}'); //a
  
//移除key
map.remove(4);
print('${map[4]}'); //null  ,当key不存在时,返回null

Runes

在Dart中,符文是字符串的UTF-32编码点。

Unicode为世界上所有的书写系统中使用的每个字母、数字和符号定义一个惟一的数字值。由于Dart字符串是UTF-16代码单元的序列,因此在字符串中表示32位Unicode值需要特殊的语法。

表达Unicode编码点的常用方法是\uXXXX,其中XXXX是一个4位十六进制值。例如,心字符(♥)表示为\ u2665。若要指定多于或少于4位十六进制数字,请将值放在大括号中。

String类有几个属性可以用来提取rune信息。codeUnitAt和codeUnit属性返回16位代码单元。使用runes属性获取字符串的runes。

下图来自官网
runes演示结果.png

Dart变量声明

var

var可以接收任意类型的变量(类似于java中的Object),Dart可以通过类型推导判断该变量的类型。但是需要注意的是,当使用var声明一个变量的时候直接给它赋值,该变量的类型便会确定下来,无法再改变其类型

  var variable;
  variable = 'Dart String';
  print(variable.runtimeType); //String
  variable = 10;
  print(variable.runtimeType); //int
  variable = true;
  print(variable.runtimeType); //bool
  
  var variable2 = 'Dart String 2'; //直接赋值,此时Dart类型推导认为variable2类型为String
  variable2 = 10; //编译报错:A value of type 'int' can't be assigned to a variable of type 'String'.
dynamic和Object

对于使用dynamic声明的变量,Dart也会进行类型推导从而可以动态的改变该变量的类型,而由于Object是所有类型的基类,所以任何类型都可以赋值给Object类型的变量。两者与var的不同在于即使在声明时赋值,也可以动态改变其类型

  dynamic variable = 'Dart String';
  print(variable.runtimeType); //String
  variable = 10;
  print(variable.runtimeType); //int
  variable = true;
  print(variable.runtimeType); //bool

  Object variable2 = 'Dart String';
  print(variable2.runtimeType); //String
  variable2 = 10;
  print(variable2.runtimeType); //int
  variable2 = true;
  print(variable2.runtimeType); //bool

前面List的声明方式,是可以添加任何类型的对象的

 //由于未给List指定类型,所以实际是下面的声明方式为List<dynamic> list = [1,2,3,4];
  List list = [1,2,3,4];
  list.add(null);
  list.add("str");
  list.add(true);
  print(list); //[1, 2, 3, 4, 5, null, str, true]

  var element;
  for(element in list){
    print('元素$element 的类型为${element.runtimeType}');
  }
  /*
元素1 的类型为int
元素2 的类型为int
元素3 的类型为int
元素4 的类型为int
元素5 的类型为int
元素null 的类型为Null
元素str 的类型为String
元素true 的类型为bool
*/

var list2 = [1,2,3]; //此时list2的类型实际上为List<int>
list2.add(true);//编译报错:The argument type 'bool' can't be assigned to the parameter type 'int'.
Final和const

对于一个只需要初始化一次,就无需再次改变的变量,请使用finalconstfinalconst不是一种类型,也不像var那样可以动态改变类型。final声明的变量只能赋值一次,在第一次访问的时候被初始化。而const声明的变量是编译时常量

final f = 'Dart String';
f = "d"; //编译报错:'f', a final variable, can only be set once

const c = 'Dart String';
c = 'd'; //编译报错:Constant variables can't be assigned a value(常量变量无法赋值)

final f1; //编译报错:The final variable 'f1' must be initialized.
const c1; //编译报错:The const variable 'c1' must be initialized.

const关键字不仅仅用于声明常量变量。它还可以用来创建常量值,以及声明创建常量值的构造函数。任何变量都可以有一个常量值

dynamic d = const [1,2,3]; //将常量值赋值给变量
//你可以改变一个非final,非const变量的值,即使它曾经有一个const值
d = 'Dart String'; //对于d来说它依旧是dynamic类型的,依旧可以动态改变类型

final f = const [1,2,3]; //将常量值赋值给final声明的变量
Default value

未初始化的变量的初始值为null。即使是int类型的变量未初始化时也是null,因为Dart中,所有东西都是对象

int i;
print(i); //null
bool b;
print(b);//null

Dart函数

还是那句话:Dart中,所有东西都是对象,所以函数也不例外。函数也是一个对象,并且有一个类型Function。这意味着函数可以被赋值给变量,也可以作为参数传递给其他函数

  • 函数声明:在Dart中,函数默认是公共的,以 _ 开头表示私有的
 int add(int a, int b) {
    return a + b;
  }

  //私有的
  int _mod(int a, int b) {
    return a % b;
  }
  • 函数返回类型省略: Dart允许您在许多地方省略类型注释,并尝试为您推断类型。在某些情况下,如果推理失败,它会默认类型为dynamicDart 类型
  var s = 10; //前面已经说过,对于这种写法,此时s的类型实际上已经确定为int
  s = getVersionName(); // 对于省略了返回类型的函数而言,默认的类型为dynamic,所以编译期并不会报错,运行时报错
  s = "sss";  //这种写法直接编译报错:A value of type 'String' can't be assigned to a variable of type 'int'.

 getVersionName(){
  return "v1.0";
 }
函数省略返回类型.png
函数省略返回类型测试结果.png
  • 对于只包含一个表达式的函数,可以使用缩写语法: =>
getVersionName() => "v1.0";
  • 函数可以有两种类型的参数:必需参数和可选参数。首先列出所需参数,然后列出可选参数。命名的可选参数也可以标记为@required

  • 可选参数:可选参数分为位置参数命名参数,但不能两者都是

  • 可选命名参数:定义一个函数,使用{param1, param2,…}指定命名参数

//a为必须参数,b,c为命名参数
String append(String a, {String b, String c}) {
  return "$a$b$c";
}

//@required表明命名参数a是必须的
String append1({@required String a,String b, String c}){
  return "$a$b$c";
}



main() {
   print(append("a")); //anullnull

   print(append(b: "b",c: "c"));//编译报错:1 required argument(s) expected, but 0 found.

   print(append("a",b: "b")); //abnull

   print(append("a",c: "c")); //anullc

  print(append("a", b: "b", c: "c")); //abc
}
  • 可选位置参数:定义一个函数,使用[param1, param2,…]指定命名参数
String append2(String a, [String b, String c]) {
  return "$a$b$c";
}

main() {
  print(append2("a")); //anullnull
  print(append2("a","b")); //abnull
  print(append2("a","b","c"));//abc
}
  • 函数默认参数值:可选参数都有一个默认值为null,并且在定义时可指定其默认值,需要注意的是指定的默认值必须是编译时常量
String append3(String a, {String b = "b", String c}) {
  return "$a$b$c";
}

main() {
   print(append3("a")); //abnull
}
  • 函数作为参数传递
    在Dart中,函数也可以作为参数传递给另外的函数
void printElement(int element) {
  print(element);
}

var list = [1, 2, 3];

list.forEach(printElement); //1 2 3

//forEach方法
/**
  * Applies the function [f] to each element of this collection in iteration
  * order.
  */
void forEach(void f(E element)) {
   for (E element in this) f(element);
}

  • 匿名函数
    大多数函数都是命名的。除此之外还可以创建一个名为匿名函数的无名函数,有时也称为lambda表达式或闭包
//对于上面的函数作为参数传递中的例子而言,创建了一个命名函数printElement传入forEach方法,相对来说是比较冗余的做法

//正常的写法是创建一个匿名函数
 var list = [1, 2, 3];
 list.forEach((element) {
    print('$element');
  });

常用操作符

  • / :除,返回值是double
  • ~/ :除,返回值是int
  var a = 5/2;
  var b = 6/2;
  var c = 5~/2;
  var d = 6~/2;

  print("$a:type is ${a.runtimeType}"); //2.5:type is double
  print("$b:type is ${b.runtimeType}"); //3.0:type is double
  print("$c:type is ${c.runtimeType}"); //2:type is int
  print("$d:type is ${d.runtimeType}"); //3:type is int
  • is : 判断对象是否包含指定的类型
  • is! : 对象包含指定类型时返回false
  • as : 类型转换(也用于指定库前缀)
import 'dart:math' as mm;  //as指定库前缀

main(){
var dog = Dog();

  if (dog is Animals) {
    print('${dog.name} is animals'); //dog is animals
  }

  var apple = Apple();
  if (apple is! Animals) {
    print('apple isn\'t animals');     //apple isn't animals
  }

  sayHi(dog); // Hi,i'm dog

 print(mm.min(1, 2)); //1
}

class Animals {
  var name = "animals";

  void sayHi() {
    print('Hi,i\'m $name');
  }
}

class Dog extends Animals {
  @override
  String get name => "dog";
}

class Apple {}

//需要注意的是当传入的t不是Animals类型或不是Animals的子类,报错。此处仅作演示
void sayHi<T>(T t) {
  (t as Animals).sayHi();
}
  • ?: :使用方式为 condition ? expr1 : expr2 ,当condition 为true时返回expr1,否则返回expr2
  • ?? :使用方式为 expr1 ?? expr2,如果expr1不为空,返回expr1,否则返回expr2
  • ?. : 有条件的成员访问,当访问成员变量或成员方法时,常用 Object.params,如果Object为空,报错,对于Object?.params,如果Object为空,不执行调用,该操作返回null
  • .. :级联,类似于Builder模式,形成链式调用
var human = Human();
human
    ..name = 'human'
    ..sex = 'male'
    ..age = 25;
var result = human.age>20 ? "yes" : "no"; //yes
var result1 =  human.sex == 'female' ? "yes" : "no"; //no
var result2 = human.girlfriend?? "get null"; //get null
var result3 = human.name ?? "get null"; //human


var human1;
var result4 = human1?.name;   // null

class Human {
  var name = 'human';
  var sex;
  var age;
  var girlfriend;
}

控制流程

  • if-else
var sex = "male";
  if (sex == "male") {
    print('he is a man');
  } else if (sex == "female") {
    print('she is a woman');
  } else {
    print('unknow');
  }
  • switch

  var type = "A";
  //需要注意的是case中的类型必需与type的类型一致
  switch (type) {
    case "A":
      print('type is A');
      break;
    case "B":
      print('type is B');
      break;
    case "C":
      print('type is C');
      break;
    default:
      break;
  }
  • for
 var array = [1, 2, 3, 4];
  for (int i = 0; i < array.length; i++) {
    print(element);
  }

  for (var i in array) {
    print(i);
  }

  array.forEach((element) {
    print(element);
  });

  //还可以简写如下
  array.forEach((element) => print(element));
  • while
  var sum = 0;
  while (sum < 10) {
    sum++;
  }
  print(sum); //10
  • try-catch
var array = [1, 2, 3, 4];
//捕获所有异常
  try {
    print(array[10]);
  } catch (e) {
    print(e);
  }finally{
    print('finally block');
  }
//RangeError (index): Invalid value: Not in range 0..3, inclusive: 10
//finally block

//捕获指定异常
  try {
    print(array[4]);
  } on RangeError  {
    print(RangeError);
  }finally{
    print('finally block');
  }
//RangeError
//finally block


  • 类定义与构造方法
class Person {
  String name;
  int _age;

  Person(String name, int age) {
    this.name = name;
    this._age = age;
  }

}
  • 在Dart中没有private、public这些成员访问修饰符,如果需要是私有的,不被外部访问可以通过在前面添加_下划线表示私有,如_age所示,私有方法同理。
    另外需要注意,在Dart中,构造方法不能重载,也就是不能出现两个同名的构造函数
  • 上面的写法还有一种更加简便的写法
class Person {
  String name;
  int _age;

  Person(this.name, this._age); //与上面的写法等价
}
  • 前面说过构造方法不能重载,那么想要有多个构造方法应该怎么处理呢?为此,Dart提供了命名构造方法
class Person {
  String name;
  int _age;

  Person(this.name, this._age);

  Person.fromJson(Map data) {
    this.name = data["name"];
    this._age = data["age"];
  }
}

main(){
  Person p = Person.fromJson({"name": "aaa", "age": 10});
  print("p.name:${p.name},p._age:${p._age}"); //p.name:aaa,p._age:10
}
  • 构造方法重定向:构造方法可以调动类中的其他构造方法来实例化
class Person {
  String name;

  int _age;

  Person(this.name, this._age);

  //命名构造函数
  Person.name(this.name);

//命名构造方法重定向到同名构造方法,实际上调用了Person(this.name, this._age);
  Person.age(int age) :this("", age);
}
  • 工厂构造函数
    创建一个实例,如果已经存在一个实例就返回缓存的实例,或者想要返回不同的对象,可以用factory修饰构造函数。它会告诉Dart这是一个工厂构造函数,在工厂构造函数中没有this对象,而是需要明确的返回一个对象。比如一个身份证代表一个Person。输入一样的身份证,希望返回同样的Person对象。
class Person {
  final String idCard;
  static Map<String, Person> _cache;

  factory Person(String idCard) {
    if (_cache == null) {
      _cache = {};
    }
    if (_cache.containsKey(idCard)) {
      return _cache[idCard];    //加了factory之后需要明确的返回一个对象
    } else {
      final Person p = new Person._instance(idCard);
      _cache[idCard] = p;  
      return p;   //加了factory之后需要明确的返回一个对象
    }
  }

  //私有的命名构造函数
  Person._instance(this.idCard);
}

main(){
  Person p = new Person("4416**199*1201****");
  Person p1 = new Person("4416**199*1201****");
  Person p2 = new Person("aa");
  print(p == p1); //true
  print(p == p2); //false
}
  • 常量构造方法:可以构造一个状态永远不变的对象--编译时常量对象
class PI {
  final num value;

  //const 修饰,表明是常量构造方法
  const PI(this.value);

  //编译时常量对象,需要使用const来创建对象
  static final PI pi = const PI(3.14);
}

main() {
  print(PI.pi.value);
}
  • 继承
    与Java中相似,使用关键字extends继承父类,使用关键字super引用父类
class Animal {
  String name;

  Animal(this.name);

  void eat() {
    print("$name eat");
  }
}

class Dog extends Animal {
  
  Dog(String name) : super(name);

  @override
  void eat() {
    super.eat();
  }
}
  • Mixin继承机制
    Dart中支持多继承,使用with关键字,一个子类可以继承多个父类
class A{
  void a(){
    print('a');
  }
}

class B{
  void b(){
    print('b');
  }
}

class C{
  void c(){
    print('c');
  }
}

class D extends A with B,C{}

main(){
  D d = new D();
  d.a();  //a
  d.b(); //b
  d.c(); //c
}
  • 上面的继承等价于这种写法
class D with A, B, C {}
  • 支持多继承,那么如果多个父类中有同样的的方法,调用的是哪一个呢
class A {
  void action() {
    print('a doSomething');
  }
}

class B {
  void action() {
    print('b doSomething');
  }
}

class C {
  void action() {
    print('c doSomething');
  }
}

class D with A, B, C {}
class E with A, C, B {}
class F with B, C, A {}

main(){
  D d = new D();
  d.action(); //c doSomething
  
  E e = new E();
  e.action(); //b doSomething

  F f = new F();
  f.action(); //a doSomething
}
  • 可以看到,当继承多个父类中有一样的方法时,调用的是继承的最后一个父类的方法

异步支持

Dart库中包含许多返回Future或Stream对象的函数。 这些函数是异步的:它们在设置一个可能非常耗时的操作(例如I / O)后返回,而不需要等待该操作完成。

  • 有两种方式来实现异步

    1. 使用asyncawait
    2. 使用Future API
  • Future
    在了解Future时,不得不提一下FutureOr<T>,因为Future API里面到处都是它的身影,了解它有助于Future API的使用。先来看一下它的定义

abstract class FutureOr<T> {
  // Private generative constructor, so that it is not subclassable, mixable, or
  // instantiable.
  FutureOr._() {
    throw new UnsupportedError("FutureOr can't be instantiated");
  }
}
  • 首先它可以表示类型Future<T>或是类型T

  • 这个类声明是内部future-or-value泛型类型的公共替代。对该类的引用被解析为内部类型

  • 在非强模式情况下,FutureOr被解释为dynamic

  • Future的定义

[Future]用来表示将来某个时候可能出现的潜在值或错误。一旦值或错误可用,[Future]的接收者可以注册回调来处理它。

我们可以简单的认为Future是一个任务,该任务会返回我们需要的结果。这个任务的执行可能是耗时的。而任务的执行过程也可能出现错误。一个Future对应一个结果,要么成功,要么失败。一般使用方法如下

Future<T> future = getFuture();
future.then((value) => handleValue(value))
         .catchError((error) => handleError(error));
  • Future的常用API
Future.then
/**
 * Register callbacks to be called when this future completes.
 */
  Future<R> then<R>(FutureOr<R> onValue(T value), {Function onError});

当任务完成时会回调,能够获取任务执行的返回值并进行处理

  //接收结果并处理,假设 getFuture()是一个网络请求等延时操作

  Future<int> future = getFuture();
  future.then((value) => print(value)); //1

  Future<int> getFuture() {
    return Future.value(1);
  }
//也能直接处理异常

 Future<int> future = getFuture();
  future.then((value) => print(value),onError:(e){print(e);});   //-1

Future<int> getFuture() {
  return Future.error(-1); 
}
Future.catchError
 /**
   * This is the asynchronous equivalent of a "catch" block.
   *
   * If `test` returns false, the exception is not handled by this `catchError`,
   * and the returned future completes with the same error and stack trace as this future.
   *
   * If `test` returns `true`,[onError] is called with the error and possibly stack trace,
   *  and the returned future is completed with the result of this call in exactly the same way as for [then]'s `onError`.
   *  
   * If `test` is omitted, it defaults to a function that always returns true.
   *
   * If the first `catchError` (or `then`) call happens after this future
   * has completed with an error then the error is reported as unhandled error.
   */

 Future<T> catchError(Function onError, {bool test(Object error)});

相当于一个catch块,捕获异常,区别多了个可选参数test来操作是否要处理该异常。如果可选参数test方法返回false,则该catchError方法不处理异常,直接抛出去让别人处理,如果返回true,就会由catchErroronError函数处理,并且使用与thenonError完全相同的方法来完成返回的future。test默认返回true。需要注意的是,如果在调用catchError之前错误就已经发生了,该错误会被当做未处理异常而不会被catchError捕获

  Future<int> future = getFuture();
  future.then((value) => print(value))
        .catchError((e){print(e);});  //-1

  Future<int> getFuture() {
    return Future.error(-1);
  }
  Future<int> future = getFuture();  //直接报错抛异常
  future.then((value) => print(value)).catchError((e) {
    print(e);
  });

Future<int> getFuture() {
  throw Exception("error");
//  return Future.error(-1);
}
Future.delayed
 /**
   * Creates a future that runs its computation after a delay.
   *
   * The [computation] will be executed after the given [duration] has passed,
   * and the future is completed with the result of the computation.
   */
 factory Future.delayed(Duration duration, [FutureOr<T> computation()])

延时执行任务

  Future.delayed(new Duration(seconds: 2),(){
    print('延时2s执行');});   //两秒后输出
}
 Future.delayed(new Duration(seconds: 2)).then((_){
    print('延时2s执行');
  });
//在这由于Exception延时2s才抛出,所以会被then的'onError'捕获

Future.delayed(new Duration(seconds: 2), () {
    throw Exception("error");
  }).then((_) {
    print('成功执行');
  }, onError: (e) {
    print('onError 错误');
  }).catchError((e1) {
    print('catchError 错误');
  });
Future.wait
/**
   * Waits for multiple futures to complete and collects their results.
   *
   * Returns a future which will complete once all the provided futures have completed,
   * either with their results, or with an error if any of the provided futures fail.
   *
   * The value of the returned future will be a list of all the values that
   * were produced in the order that the futures are provided by iterating
   * [futures].
   *
   * If any future completes with an error,
   * then the returned future completes with that error.
   *
   *
   * If `eagerError` is true, the returned future completes with an error immediately on the first error from one of the futures.
   * Otherwise all futures must complete before the returned future is completed (still with the first error; the remaining errors are silently dropped).
   * 
   * In the case of an error, [cleanUp] (if provided), is invoked on any non-null result of successful futures. This makes it possible to `cleanUp` resources that would otherwise be lost (since the returned future does not provide access to these values). The [cleanUp] function is unused if there is no error.
   */
 static Future<List<T>> wait<T>(Iterable<Future<T>> futures,{bool eagerError: false, void cleanUp(T successValue)})

如果有多个延时任务,想要等它们都完成才执行别的任务,可以用Future.wait实现。所有Future任务会按照提供的顺序以集合的形式将任务结果返回。如果其中的部分任务出错了会怎么处理呢?eagerErrortrue,当出现第一个错误的时就会立即返回一个错误,如果为false,所有的任务都会被执行,任务都完成后依旧返回第一个错误,其它的错误会被丢弃。eagerError默认为false。任务有出错,wait会返回一个错误,那么对于其中成功的任务的返回值,都可以由cleanUp接收,在里面可以做额外的处理,比如资源的释放等。

 Future.wait([
    Future.delayed(new Duration(seconds: 1), () {
      print('task one');
      return Future.value(1);
    }),
    Future.delayed(new Duration(seconds: 3), () {
      print('task two');
      return Future.value(2);
    }),
    Future.delayed(new Duration(seconds: 5), () {
      print('task three');
      return Future.value(3);
    })
  ]).then((listValue) {
    listValue.forEach((element) {
      print(element);
    });
  });

结果如下:

future.wait.png
 Future.wait([
    Future.delayed(new Duration(seconds: 1), () {
      print('task one');
      return Future.value(1);
    }),
    Future.delayed(new Duration(seconds: 3), () {
      print('task two');
      return Future.error(-2);
    }),
    Future.delayed(new Duration(seconds: 5), () {
      print('task three');
      return Future.value(3);
    }),
    Future.delayed(new Duration(seconds: 7), () {
     print('task four');
      return Future.error(-4);
    })
  ],eagerError: false,cleanUp: (successValue){
    print('接收到$successValue,回收资源');
  }).then((listValue) {
    listValue.forEach((element) {
      print(element);
    });
  }).catchError((e)=> print("捕获到错误:$e"));

结果如下,等所有任务执行完成,只返回第一个错误

clearUp处理.png

再来看下eagerErrortrue的情况

 Future.wait([
    Future.delayed(new Duration(seconds: 1), () {
      print('task one');
      return Future.value(1);
    }),
    Future.delayed(new Duration(seconds: 3), () {
      print('task two');
      return Future.error(-2);
    }),
    Future.delayed(new Duration(seconds: 5), () {
      print('task three');
      return Future.value(3);
    }),
    Future.delayed(new Duration(seconds: 7), () {
      print('task four');
      return Future.error(-4);
    })
  ],eagerError: true,cleanUp: (successValue){
    print('接收到$successValue,回收资源');
  }).then((listValue) {
    listValue.forEach((element) {
      print(element);
    });
  }).catchError((e)=> print("捕获到错误:$e"));

结果如下,可以看到当出现错误时会立即被捕获到

eagerError为true.png

async和await

  • 在Dart中使用asyncawait关键字来实现异步编程,可以通过它们编写看起来类似于同步代码的异步代码。被async修饰的函数会返回一个future。注意await必须在async块中使用。
    那么它们怎么使用呢?假设有这么一个场景,你需要从网络中下载一张图片,下载下来需要进行裁剪压缩或美颜等处理,然后再保存到本地。
//定义任务

Future<File> getImageFile(String url) {
  return Future.delayed(new Duration(seconds: 2), () {
    print('下载图片');
    return new File(url);
  });
}


Future<File> handleFile(File file) {
  return Future.delayed(new Duration(seconds: 2), () {
    print('处理图片');
    return file;
  });
}

Future<bool> saveFile(File file) {
  return Future.delayed(new Duration(seconds: 2), () {
    print('保存图片图片');
    return true;
  });
}

如果不熟悉Future,可能会出现嵌套调用

  getImageFile("***********").then((file) {
    handleFile(file).then((file2) {
      saveFile(file);
    });
  }).catchError((e) {
    print(e);
  });

实际上Future本身支持链式调用,可以让结构看起来更加清晰

 getImageFile("***********").then((file) {
    return handleFile(file);
  }).then((file2) {
    return saveFile(file2);
  }).catchError((e) {
    print(e);
  });

不管怎么链式调用,实际上还是会有多一层回调,而使用asyncawait,则可以像写同步代码一样处理异步操作

action() async {
  try {
    File file = await getImageFile("***********");
    File file2 = await handleFile(file);
    bool result = await saveFile(file2);
  } catch (e) {
    //
  } finally {
    //
  }
}

结果如下

20200117151319.png
  • streams

    与Future一样,Stream也是dart中实现异步编程的一种利器。Stream实际上是一个异步事件流,它就像一个事件的管道一样,你可以向管道的一端插入事件,事件会在管道内流动,然后从管道中的另外一端流出。与Future不同的是,Stream更像是一个异步序列,并不是你主动去请求得到事件,而是当一个异步事件准备好的时候,流会主动通知你事件的到来。Future和Stream都是处理异步操作的。那么它们有什么不同呢?前面我们知道了Future对应着一种结果,不管是我们期望的值或者是一种错误,比如我们进行一个网络请求操作,一种是请求成功,获取到我们需要的数据,一种是由于网络或是其它因素导致的错误结果。网络请求是耗时的,所以我们可以利用Future来代表这一个操作结果,当然它不仅仅是代表一种结果,还提供了链式调用、一系列其它API,使得对结果或错误的后续处理更加的方便等功能。

    而Stream呢?考虑这样一种场景,A对B产生的某些事件很感兴趣,但是A不知道B什么时候会产生(发出)事件,也不知道B会产生多少事件,也不知道产生事件的耗时。B也不知道A需要什么事件,并且A需要在B发出事件的时候就作出响应。既然都不知道,A也不能一直停在那就等着B的事件发出,还是要处理自己的其它事情的。那么怎么处理这种情况呢?有办法,在A和B之间架一个管道,给B提供一个事件的入口,当有事件触发的时候,B将事件由入口提供给管道,事件在管道内流动来到出口(实际上在流动的过程可以对事件进行一些变换处理),A只需要监听管道的出口,有需要的事件就处理。同时这一个过程是异步的,也就是说并不会影响A处理其它事情,而是等待到有事件过来才处理。当然当A不感兴趣了还要取消监听。而Stream正是解决这一场景的这么一个工具。有时候不单只有A对B的事件感兴趣,还有C、D等多个对象一起监听,所以Stream又分两种:single subscription(单一订阅) 、 broadcast (广播)
    具体的使用场景,比如Flutter页面与Native页面之间的通信、Flutter提供的基于Stream提供的数据构建的Widget:StreamBuilderBloc模式、flsh_redux实现业务逻辑与UI分离、RxDart等都离不开Stream

  • 流的使用一般会涉及到四个对象:StreamControllerStreamSinkStreamSubscriptionStream

    StreamController:带有它所控制的stream的控制器,它本身允许发送数据、错误、事件到它所控制的stream。并且提供了一系列创建各种事件流的API

    StreamSink: 一般作为流的入口,它可以接收同步或异步的流事件

    Stream: 流本身,也就是我们说的管道,提供监听的方法(listen)、以及事件的转换

    StreamSubscription: 对于流上的事件的订阅,当你使用Stream.listen监听Stream时,返回一个StreamSubscription对象,提供取消订阅或者暂停流中的事件的功能

 //创建控制器
  StreamController _controller = StreamController();

  //获取控制器控制流的入口
  StreamSink sink = _controller.sink;

  //获取控制器控制的流
  Stream stream = _controller.stream;

  //监听流,返回订阅对象
  StreamSubscription streamSubscription = stream.listen((onData) {
    print(onData);
  });
  //错误监听
  streamSubscription.onError((e) {
    print(e);
  });
  streamSubscription.onDone((){
    print("done");
  });

  //传递事件
  _controller.add("controller add");
  _controller.add(1);
  sink.add("sink add");
  sink.add(2);
  sink.addError(Exception("error"));

   //暂停
//  streamSubscription.pause();
   //恢复
//  streamSubscription.resume();
  //取消订阅
//  streamSubscription.cancel();
  • 结果如下
stream使用.png
  • 常见获取Stream方式

  • 构造方法

    Stream.empty()
    生成一个空的广播流

    Stream.error(Object error, [ StackTrace stackTrace ])
    创建一个在完成前发出单个错误事件的流

    Stream.eventTransformed(Stream source, EventSink mapSink(EventSink<T> sink))
    创建一个流,其中将现有流的所有事件通过接收器转换通过管道传输

    Stream.periodic(Duration period, [ T computation(int computationCount) ])
    创建一个流,该流以一定period间隔重复发出事件

    Stream.fromFuture(Future<T> future)
    从Future创建新的单订阅流

    Stream.fromFutures(Iterable<Future<T>> futures)
    从一组Future创建一个流

    Stream.fromIterable(Iterable<T> elements)
    创建一个单订阅流,从elements获取数据

    Stream.value(T value)
    创建一个在完成前发出单个数据事件的流

  • 使用StreamController
    通过StreamController的stream属性获取其控制的流

    StreamController的工厂方法StreamController.broadcast( {void onListen(), void onCancel(), bool sync: false})

  • 接收流事件
    前面了解了常见的流创建方式,对于接收流,除了stream.listen监听,还可以使用异步for循环(通常仅称为await for)对流的事件进行迭代,就像for循环迭代一样。遍历。在await for 中可以使用returnbreak 来中断流。注意使用await for需要使用async关键字标记

import 'dart:async';

Future<int> sumStream(Stream<int> stream) async {
  var sum = 0;
  await for (var value in stream) {
    sum += value;
  }
  return sum;
}

//新建一个返回 Stream 的异步函数需要使用 async* 来标记, 使用 yield 或 yield* 来发射数据
Stream<int> countStream(int to) async* {
  for (int i = 1; i <= to; i++) {
    yield i;
  }
}

main() async {
  var stream = countStream(10);
  var sum = await sumStream(stream);
  print(sum); // 55
}

  • 流的种类
    流有两种:单一订阅流、广播流

    单一订阅流包含一系列事件,这些事件是较大整体的一部分。必须以正确的顺序交付事件,并且不能错过任何事件。比如读取文件或接收Web请求时获得的流。另外只能单一订阅流被收听一次。稍后再次收听可能意味着错过了最初的事件,然后其余部分毫无意义。注意即使是取消了上一次订阅,也无法再次重新订阅

    广播流是针对可以一次处理的单个消息的,例如浏览器中的鼠标事件。可以随时开始收听这样的流,并且在收听时会触发事件。多个收听者可以同时收听。可以在取消上一个订阅之后稍后再次收听

  • 处理流的方法
    Stream <T>上的以下方法处理流并返回结果:

Future<T> get first;
Future<bool> get isEmpty;
Future<T> get last;
Future<int> get length;
Future<T> get single;
Future<bool> any(bool Function(T element) test);
Future<bool> contains(Object needle);
Future<E> drain<E>([E futureValue]);
Future<T> elementAt(int index);
Future<bool> every(bool Function(T element) test);
Future<T> firstWhere(bool Function(T element) test, {T Function() orElse});
Future<S> fold<S>(S initialValue, S Function(S previous, T element) combine);
Future forEach(void Function(T element) action);
Future<String> join([String separator = ""]);
Future<T> lastWhere(bool Function(T element) test, {T Function() orElse});
Future pipe(StreamConsumer<T> streamConsumer);
Future<T> reduce(T Function(T previous, T element) combine);
Future<T> singleWhere(bool Function(T element) test, {T Function() orElse});
Future<List<T>> toList();
Future<Set<T>> toSet();
  • 修改流的方法
    Stream上的以下方法基于原始流返回新的流
Stream<R> cast<R>();
Stream<S> expand<S>(Iterable<S> Function(T element) convert);
Stream<S> map<S>(S Function(T event) convert);
Stream<T> skip(int count);
Stream<T> skipWhile(bool Function(T element) test);
Stream<T> take(int count);
Stream<T> takeWhile(bool Function(T element) test);
Stream<T> where(bool Function(T event) test);

参考

Language Tour | Dart

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容