Flutter筆記--Dart基礎(chǔ)語法

Dart中一切皆對象

Dart內(nèi)置類型

Numbers

Numbers類型包含兩種:
int :int類型不超過64位,根據(jù)平臺的不同,取值范圍不同。在Dart虛擬機(jī)中int取值范圍為-2632 63,編譯成JavaScript的Dart使用 JavaScript numbers,允許從-2 53253 - 1的值
double :64位(雙精度)浮點(diǎn)數(shù),由IEEE 754標(biāo)準(zhǔn)指定

  //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,必要的話,整型會自動轉(zhuǎn)換為雙精度
  double z = 1;  //相當(dāng)于double z = 1.0, 在Dart2.1之前這種寫法是會報錯的
String

字符串創(chuàng)建方式:‘單引號創(chuàng)建’,“雙引號創(chuàng)建”,'''三個單引號(有格式的)''',"""三個雙引號(有格式的)""",以r為前綴的原始字符串

//單引號和雙引號比較常用
String s = 'single quotes';
String d = "double quotes";

//三個單引號和三個雙引號是一樣的
String t = '''three single quotes''';
String t1 = """three double quotes""";

//三個單、雙引號字符串一般用于創(chuàng)建多行字符串,即
  String t2= '''$t
         can be styled virtually
 any way you want ''';

//輸出結(jié)果即包含輸入的樣式
  print(t2);
  /**

      three single quotes
      can be styled virtually
      any way you want
   */
//在單雙引號或三單引號以及三雙引號中$、\n等特殊字符還是會特殊處理的,而對于以r為前綴的原始字符串來說,任何字符都不會得到區(qū)別對待
  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.
     */

//其中 ${表達(dá)式} 可以獲取到該表達(dá)式的值,另外該表達(dá)式本身是一個變量時可省略{}

另外再說一下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('==操作符可用于判斷兩個對象是否相等,當(dāng)兩個字符串包含的字符一致時,這兩個字符串是相等的');
  }

Lists

在Dart中,Array也是List,所以統(tǒng)稱為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)關(guān)聯(lián)起來的對象。key和value都可以是任何類型的對象,在一個map中,每個key只能出現(xiàn)一次,但是多個key,可以對應(yīng)同樣的value

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

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

//根據(jù)key獲取value
print('${map[1]}'); //a
  
//移除key
map.remove(4);
print('${map[4]}'); //null  ,當(dāng)key不存在時,返回null

Runes

在Dart中,符文是字符串的UTF-32編碼點(diǎn)。

Unicode為世界上所有的書寫系統(tǒng)中使用的每個字母、數(shù)字和符號定義一個惟一的數(shù)字值。由于Dart字符串是UTF-16代碼單元的序列,因此在字符串中表示32位Unicode值需要特殊的語法。

表達(dá)Unicode編碼點(diǎn)的常用方法是\uXXXX,其中XXXX是一個4位十六進(jìn)制值。例如,心字符(?)表示為\ u2665。若要指定多于或少于4位十六進(jìn)制數(shù)字,請將值放在大括號中。

String類有幾個屬性可以用來提取rune信息。codeUnitAt和codeUnit屬性返回16位代碼單元。使用runes屬性獲取字符串的runes。

下圖來自官網(wǎng)
runes演示結(jié)果.png

Dart變量聲明

var

var可以接收任意類型的變量(類似于java中的Object),Dart可以通過類型推導(dǎo)判斷該變量的類型。但是需要注意的是,當(dāng)使用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類型推導(dǎo)認(rèn)為variable2類型為String
  variable2 = 10; //編譯報錯:A value of type 'int' can't be assigned to a variable of type 'String'.
dynamic和Object

對于使用dynamic聲明的變量,Dart也會進(jìn)行類型推導(dǎo)從而可以動態(tài)的改變該變量的類型,而由于Object是所有類型的基類,所以任何類型都可以賦值給Object類型的變量。兩者與var的不同在于即使在聲明時賦值,也可以動態(tài)改變其類型

  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指定類型,所以實(shí)際是下面的聲明方式為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的類型實(shí)際上為List<int>
list2.add(true);//編譯報錯:The argument type 'bool' can't be assigned to the parameter type 'int'.
Final和const

對于一個只需要初始化一次,就無需再次改變的變量,請使用finalconst。finalconst不是一種類型,也不像var那樣可以動態(tài)改變類型。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關(guān)鍵字不僅僅用于聲明常量變量。它還可以用來創(chuàng)建常量值,以及聲明創(chuàng)建常量值的構(gòu)造函數(shù)。任何變量都可以有一個常量值

dynamic d = const [1,2,3]; //將常量值賦值給變量
//你可以改變一個非final,非const變量的值,即使它曾經(jīng)有一個const值
d = 'Dart String'; //對于d來說它依舊是dynamic類型的,依舊可以動態(tài)改變類型

final f = const [1,2,3]; //將常量值賦值給final聲明的變量
Default value

未初始化的變量的初始值為null。即使是int類型的變量未初始化時也是null,因?yàn)?strong>Dart中,所有東西都是對象

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

Dart函數(shù)

還是那句話:Dart中,所有東西都是對象,所以函數(shù)也不例外。函數(shù)也是一個對象,并且有一個類型Function。這意味著函數(shù)可以被賦值給變量,也可以作為參數(shù)傳遞給其他函數(shù)

  • 函數(shù)聲明:在Dart中,函數(shù)默認(rèn)是公共的,以 _ 開頭表示私有的
 int add(int a, int b) {
    return a + b;
  }

  //私有的
  int _mod(int a, int b) {
    return a % b;
  }
  • 函數(shù)返回類型省略: Dart允許您在許多地方省略類型注釋,并嘗試為您推斷類型。在某些情況下,如果推理失敗,它會默認(rèn)類型為dynamic。Dart 類型
  var s = 10; //前面已經(jīng)說過,對于這種寫法,此時s的類型實(shí)際上已經(jīng)確定為int
  s = getVersionName(); // 對于省略了返回類型的函數(shù)而言,默認(rèn)的類型為dynamic,所以編譯期并不會報錯,運(yùn)行時報錯
  s = "sss";  //這種寫法直接編譯報錯:A value of type 'String' can't be assigned to a variable of type 'int'.

 getVersionName(){
  return "v1.0";
 }
函數(shù)省略返回類型.png
函數(shù)省略返回類型測試結(jié)果.png
  • 對于只包含一個表達(dá)式的函數(shù),可以使用縮寫語法: =>
getVersionName() => "v1.0";
  • 函數(shù)可以有兩種類型的參數(shù):必需參數(shù)和可選參數(shù)。首先列出所需參數(shù),然后列出可選參數(shù)。命名的可選參數(shù)也可以標(biāo)記為@required

  • 可選參數(shù):可選參數(shù)分為位置參數(shù)命名參數(shù),但不能兩者都是

  • 可選命名參數(shù):定義一個函數(shù),使用{param1, param2,…}指定命名參數(shù)

//a為必須參數(shù),b,c為命名參數(shù)
String append(String a, {String b, String c}) {
  return "$a$b$c";
}

//@required表明命名參數(shù)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
}
  • 可選位置參數(shù):定義一個函數(shù),使用[param1, param2,…]指定命名參數(shù)
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
}
  • 函數(shù)默認(rèn)參數(shù)值:可選參數(shù)都有一個默認(rèn)值為null,并且在定義時可指定其默認(rèn)值,需要注意的是指定的默認(rèn)值必須是編譯時常量
String append3(String a, {String b = "b", String c}) {
  return "$a$b$c";
}

main() {
   print(append3("a")); //abnull
}
  • 函數(shù)作為參數(shù)傳遞
    在Dart中,函數(shù)也可以作為參數(shù)傳遞給另外的函數(shù)
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);
}

  • 匿名函數(shù)
    大多數(shù)函數(shù)都是命名的。除此之外還可以創(chuàng)建一個名為匿名函數(shù)的無名函數(shù),有時也稱為lambda表達(dá)式或閉包
//對于上面的函數(shù)作為參數(shù)傳遞中的例子而言,創(chuàng)建了一個命名函數(shù)printElement傳入forEach方法,相對來說是比較冗余的做法

//正常的寫法是創(chuàng)建一個匿名函數(shù)
 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 : 類型轉(zhuǎn)換(也用于指定庫前綴)
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 {}

//需要注意的是當(dāng)傳入的t不是Animals類型或不是Animals的子類,報錯。此處僅作演示
void sayHi<T>(T t) {
  (t as Animals).sayHi();
}
  • ?: :使用方式為 condition ? expr1 : expr2 ,當(dāng)condition 為true時返回expr1,否則返回expr2
  • ?? :使用方式為 expr1 ?? expr2,如果expr1不為空,返回expr1,否則返回expr2
  • ?. : 有條件的成員訪問,當(dāng)訪問成員變量或成員方法時,常用 Object.params,如果Object為空,報錯,對于Object?.params,如果Object為空,不執(zhí)行調(diào)用,該操作返回null
  • .. :級聯(lián),類似于Builder模式,形成鏈?zhǔn)秸{(diào)用
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


  • 類定義與構(gòu)造方法
class Person {
  String name;
  int _age;

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

}
  • 在Dart中沒有private、public這些成員訪問修飾符,如果需要是私有的,不被外部訪問可以通過在前面添加_下劃線表示私有,如_age所示,私有方法同理。
    另外需要注意,在Dart中,構(gòu)造方法不能重載,也就是不能出現(xiàn)兩個同名的構(gòu)造函數(shù)
  • 上面的寫法還有一種更加簡便的寫法
class Person {
  String name;
  int _age;

  Person(this.name, this._age); //與上面的寫法等價
}
  • 前面說過構(gòu)造方法不能重載,那么想要有多個構(gòu)造方法應(yīng)該怎么處理呢?為此,Dart提供了命名構(gòu)造方法
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
}
  • 構(gòu)造方法重定向:構(gòu)造方法可以調(diào)動類中的其他構(gòu)造方法來實(shí)例化
class Person {
  String name;

  int _age;

  Person(this.name, this._age);

  //命名構(gòu)造函數(shù)
  Person.name(this.name);

//命名構(gòu)造方法重定向到同名構(gòu)造方法,實(shí)際上調(diào)用了Person(this.name, this._age);
  Person.age(int age) :this("", age);
}
  • 工廠構(gòu)造函數(shù)
    創(chuàng)建一個實(shí)例,如果已經(jīng)存在一個實(shí)例就返回緩存的實(shí)例,或者想要返回不同的對象,可以用factory修飾構(gòu)造函數(shù)。它會告訴Dart這是一個工廠構(gòu)造函數(shù),在工廠構(gòu)造函數(shù)中沒有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之后需要明確的返回一個對象
    }
  }

  //私有的命名構(gòu)造函數(shù)
  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
}
  • 常量構(gòu)造方法:可以構(gòu)造一個狀態(tài)永遠(yuǎn)不變的對象--編譯時常量對象
class PI {
  final num value;

  //const 修飾,表明是常量構(gòu)造方法
  const PI(this.value);

  //編譯時常量對象,需要使用const來創(chuàng)建對象
  static final PI pi = const PI(3.14);
}

main() {
  print(PI.pi.value);
}
  • 繼承
    與Java中相似,使用關(guān)鍵字extends繼承父類,使用關(guān)鍵字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繼承機(jī)制
    Dart中支持多繼承,使用with關(guān)鍵字,一個子類可以繼承多個父類
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 {}
  • 支持多繼承,那么如果多個父類中有同樣的的方法,調(diào)用的是哪一個呢
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
}
  • 可以看到,當(dāng)繼承多個父類中有一樣的方法時,調(diào)用的是繼承的最后一個父類的方法

異步支持

Dart庫中包含許多返回Future或Stream對象的函數(shù)。 這些函數(shù)是異步的:它們在設(shè)置一個可能非常耗時的操作(例如I / O)后返回,而不需要等待該操作完成。

  • 有兩種方式來實(shí)現(xiàn)異步

    1. 使用asyncawait
    2. 使用Future API
  • Future
    在了解Future時,不得不提一下FutureOr<T>,因?yàn)镕uture 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

  • 這個類聲明是內(nèi)部future-or-value泛型類型的公共替代。對該類的引用被解析為內(nèi)部類型

  • 在非強(qiáng)模式情況下,F(xiàn)utureOr被解釋為dynamic

  • Future的定義

[Future]用來表示將來某個時候可能出現(xiàn)的潛在值或錯誤。一旦值或錯誤可用,[Future]的接收者可以注冊回調(diào)來處理它。

我們可以簡單的認(rèn)為Future是一個任務(wù),該任務(wù)會返回我們需要的結(jié)果。這個任務(wù)的執(zhí)行可能是耗時的。而任務(wù)的執(zhí)行過程也可能出現(xiàn)錯誤。一個Future對應(yīng)一個結(jié)果,要么成功,要么失敗。一般使用方法如下

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});

當(dāng)任務(wù)完成時會回調(diào),能夠獲取任務(wù)執(zhí)行的返回值并進(jìn)行處理

  //接收結(jié)果并處理,假設(shè) getFuture()是一個網(wǎng)絡(luò)請求等延時操作

  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)});

相當(dāng)于一個catch塊,捕獲異常,區(qū)別多了個可選參數(shù)test來操作是否要處理該異常。如果可選參數(shù)test方法返回false,則該catchError方法不處理異常,直接拋出去讓別人處理,如果返回true,就會由catchErroronError函數(shù)處理,并且使用與thenonError完全相同的方法來完成返回的future。test默認(rèn)返回true。需要注意的是,如果在調(diào)用catchError之前錯誤就已經(jīng)發(fā)生了,該錯誤會被當(dāng)做未處理異常而不會被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()])

延時執(zhí)行任務(wù)

  Future.delayed(new Duration(seconds: 2),(){
    print('延時2s執(zhí)行');});   //兩秒后輸出
}
 Future.delayed(new Duration(seconds: 2)).then((_){
    print('延時2s執(zhí)行');
  });
//在這由于Exception延時2s才拋出,所以會被then的'onError'捕獲

Future.delayed(new Duration(seconds: 2), () {
    throw Exception("error");
  }).then((_) {
    print('成功執(zhí)行');
  }, 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)})

如果有多個延時任務(wù),想要等它們都完成才執(zhí)行別的任務(wù),可以用Future.wait實(shí)現(xiàn)。所有Future任務(wù)會按照提供的順序以集合的形式將任務(wù)結(jié)果返回。如果其中的部分任務(wù)出錯了會怎么處理呢?eagerErrortrue,當(dāng)出現(xiàn)第一個錯誤的時就會立即返回一個錯誤,如果為false,所有的任務(wù)都會被執(zhí)行,任務(wù)都完成后依舊返回第一個錯誤,其它的錯誤會被丟棄。eagerError默認(rèn)為false。任務(wù)有出錯,wait會返回一個錯誤,那么對于其中成功的任務(wù)的返回值,都可以由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);
    });
  });

結(jié)果如下:

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"));

結(jié)果如下,等所有任務(wù)執(zhí)行完成,只返回第一個錯誤

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"));

結(jié)果如下,可以看到當(dāng)出現(xiàn)錯誤時會立即被捕獲到

eagerError為true.png

async和await

  • 在Dart中使用asyncawait關(guān)鍵字來實(shí)現(xiàn)異步編程,可以通過它們編寫看起來類似于同步代碼的異步代碼。被async修飾的函數(shù)會返回一個future。注意await必須在async塊中使用。
    那么它們怎么使用呢?假設(shè)有這么一個場景,你需要從網(wǎng)絡(luò)中下載一張圖片,下載下來需要進(jìn)行裁剪壓縮或美顏等處理,然后再保存到本地。
//定義任務(wù)

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,可能會出現(xiàn)嵌套調(diào)用

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

實(shí)際上Future本身支持鏈?zhǔn)秸{(diào)用,可以讓結(jié)構(gòu)看起來更加清晰

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

不管怎么鏈?zhǔn)秸{(diào)用,實(shí)際上還是會有多一層回調(diào),而使用asyncawait,則可以像寫同步代碼一樣處理異步操作

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

結(jié)果如下

20200117151319.png
  • streams

    與Future一樣,Stream也是dart中實(shí)現(xiàn)異步編程的一種利器。Stream實(shí)際上是一個異步事件流,它就像一個事件的管道一樣,你可以向管道的一端插入事件,事件會在管道內(nèi)流動,然后從管道中的另外一端流出。與Future不同的是,Stream更像是一個異步序列,并不是你主動去請求得到事件,而是當(dāng)一個異步事件準(zhǔn)備好的時候,流會主動通知你事件的到來。Future和Stream都是處理異步操作的。那么它們有什么不同呢?前面我們知道了Future對應(yīng)著一種結(jié)果,不管是我們期望的值或者是一種錯誤,比如我們進(jìn)行一個網(wǎng)絡(luò)請求操作,一種是請求成功,獲取到我們需要的數(shù)據(jù),一種是由于網(wǎng)絡(luò)或是其它因素導(dǎo)致的錯誤結(jié)果。網(wǎng)絡(luò)請求是耗時的,所以我們可以利用Future來代表這一個操作結(jié)果,當(dāng)然它不僅僅是代表一種結(jié)果,還提供了鏈?zhǔn)秸{(diào)用、一系列其它API,使得對結(jié)果或錯誤的后續(xù)處理更加的方便等功能。

    而Stream呢?考慮這樣一種場景,A對B產(chǎn)生的某些事件很感興趣,但是A不知道B什么時候會產(chǎn)生(發(fā)出)事件,也不知道B會產(chǎn)生多少事件,也不知道產(chǎn)生事件的耗時。B也不知道A需要什么事件,并且A需要在B發(fā)出事件的時候就作出響應(yīng)。既然都不知道,A也不能一直停在那就等著B的事件發(fā)出,還是要處理自己的其它事情的。那么怎么處理這種情況呢?有辦法,在A和B之間架一個管道,給B提供一個事件的入口,當(dāng)有事件觸發(fā)的時候,B將事件由入口提供給管道,事件在管道內(nèi)流動來到出口(實(shí)際上在流動的過程可以對事件進(jìn)行一些變換處理),A只需要監(jiān)聽管道的出口,有需要的事件就處理。同時這一個過程是異步的,也就是說并不會影響A處理其它事情,而是等待到有事件過來才處理。當(dāng)然當(dāng)A不感興趣了還要取消監(jiān)聽。而Stream正是解決這一場景的這么一個工具。有時候不單只有A對B的事件感興趣,還有C、D等多個對象一起監(jiān)聽,所以Stream又分兩種:single subscription(單一訂閱) 、 broadcast (廣播)
    具體的使用場景,比如Flutter頁面與Native頁面之間的通信、Flutter提供的基于Stream提供的數(shù)據(jù)構(gòu)建的Widget:StreamBuilderBloc模式、flsh_redux實(shí)現(xiàn)業(yè)務(wù)邏輯與UI分離、RxDart等都離不開Stream

  • 流的使用一般會涉及到四個對象:StreamControllerStreamSink、StreamSubscription、Stream

    StreamController:帶有它所控制的stream的控制器,它本身允許發(fā)送數(shù)據(jù)、錯誤、事件到它所控制的stream。并且提供了一系列創(chuàng)建各種事件流的API

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

    Stream: 流本身,也就是我們說的管道,提供監(jiān)聽的方法(listen)、以及事件的轉(zhuǎn)換

    StreamSubscription: 對于流上的事件的訂閱,當(dāng)你使用Stream.listen監(jiān)聽Stream時,返回一個StreamSubscription對象,提供取消訂閱或者暫停流中的事件的功能

 //創(chuàng)建控制器
  StreamController _controller = StreamController();

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

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

  //監(jiān)聽流,返回訂閱對象
  StreamSubscription streamSubscription = stream.listen((onData) {
    print(onData);
  });
  //錯誤監(jiān)聽
  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();
   //恢復(fù)
//  streamSubscription.resume();
  //取消訂閱
//  streamSubscription.cancel();
  • 結(jié)果如下
stream使用.png
  • 常見獲取Stream方式

  • 構(gòu)造方法

    Stream.empty()
    生成一個空的廣播流

    Stream.error(Object error, [ StackTrace stackTrace ])
    創(chuàng)建一個在完成前發(fā)出單個錯誤事件的流

    Stream.eventTransformed(Stream source, EventSink mapSink(EventSink<T> sink))
    創(chuàng)建一個流,其中將現(xiàn)有流的所有事件通過接收器轉(zhuǎn)換通過管道傳輸

    Stream.periodic(Duration period, [ T computation(int computationCount) ])
    創(chuàng)建一個流,該流以一定period間隔重復(fù)發(fā)出事件

    Stream.fromFuture(Future<T> future)
    從Future創(chuàng)建新的單訂閱流

    Stream.fromFutures(Iterable<Future<T>> futures)
    從一組Future創(chuàng)建一個流

    Stream.fromIterable(Iterable<T> elements)
    創(chuàng)建一個單訂閱流,從elements獲取數(shù)據(jù)

    Stream.value(T value)
    創(chuàng)建一個在完成前發(fā)出單個數(shù)據(jù)事件的流

  • 使用StreamController
    通過StreamController的stream屬性獲取其控制的流

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

  • 接收流事件
    前面了解了常見的流創(chuàng)建方式,對于接收流,除了stream.listen監(jiān)聽,還可以使用異步for循環(huán)(通常僅稱為await for)對流的事件進(jìn)行迭代,就像for循環(huán)迭代一樣。遍歷。在await for 中可以使用returnbreak 來中斷流。注意使用await for需要使用async關(guān)鍵字標(biāo)記

import 'dart:async';

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

//新建一個返回 Stream 的異步函數(shù)需要使用 async* 來標(biāo)記, 使用 yield 或 yield* 來發(fā)射數(shù)據(jù)
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請求時獲得的流。另外只能單一訂閱流被收聽一次。稍后再次收聽可能意味著錯過了最初的事件,然后其余部分毫無意義。注意即使是取消了上一次訂閱,也無法再次重新訂閱

    廣播流是針對可以一次處理的單個消息的,例如瀏覽器中的鼠標(biāo)事件。可以隨時開始收聽這樣的流,并且在收聽時會觸發(fā)事件。多個收聽者可以同時收聽??梢栽谌∠弦粋€訂閱之后稍后再次收聽

  • 處理流的方法
    Stream <T>上的以下方法處理流并返回結(jié)果:

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容