Dart中一切皆對象
Dart內(nèi)置類型
Numbers
Numbers類型包含兩種:
int :int類型不超過64位,根據(jù)平臺的不同,取值范圍不同。在Dart虛擬機(jī)中int取值范圍為-263 到 2 63,編譯成JavaScript的Dart使用 JavaScript numbers,允許從-2 53到253 - 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類型,true和false,都是編譯時常量
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
對于一個只需要初始化一次,就無需再次改變的變量,請使用final和const。final和const不是一種類型,也不像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";
}


- 對于只包含一個表達(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)異步
- 使用
async和await - 使用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被解釋為
dynamicFuture的定義
[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,就會由catchError的onError函數(shù)處理,并且使用與then的 onError完全相同的方法來完成返回的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ù)出錯了會怎么處理呢?eagerError為true,當(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([
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í)行完成,只返回第一個錯誤

再來看下eagerError為true的情況
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)錯誤時會立即被捕獲到

async和await
- 在Dart中使用
async和await關(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),而使用async和await,則可以像寫同步代碼一樣處理異步操作
action() async {
try {
File file = await getImageFile("***********");
File file2 = await handleFile(file);
bool result = await saveFile(file2);
} catch (e) {
//
} finally {
//
}
}
結(jié)果如下

-
與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:StreamBuilder,Bloc模式、flsh_redux實(shí)現(xiàn)業(yè)務(wù)邏輯與UI分離、RxDart等都離不開Stream
-
流的使用一般會涉及到四個對象:StreamController、StreamSink、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方式
-
構(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中可以使用return或break來中斷流。注意使用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);
