Dart中一切皆对象
Dart内置类型
Numbers
Numbers类型包含两种:
int :int类型不超过64位,根据平台的不同,取值范围不同。在Dart虚拟机中int取值范围为-263 到 2 63,编译成JavaScript的Dart使用 JavaScript numbers,允许从-2 53到253 - 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类型,true
和false
,都是编译时常量
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。
下图来自官网
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
对于一个只需要初始化一次,就无需再次改变的变量,请使用final
和const
。final
和const
不是一种类型,也不像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允许您在许多地方省略类型注释,并尝试为您推断类型。在某些情况下,如果推理失败,它会默认类型为
dynamic
。Dart 类型
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";
}
- 对于只包含一个表达式的函数,可以使用缩写语法: =>
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)后返回,而不需要等待该操作完成。
-
有两种方式来实现异步
- 使用
async
和await
- 使用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
,就会由catchError
的onError
函数处理,并且使用与then
的 onError
完全相同的方法来完成返回的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任务会按照提供的顺序以集合的形式将任务结果返回。如果其中的部分任务出错了会怎么处理呢?eagerError
为true
,当出现第一个错误的时就会立即返回一个错误,如果为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([
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"));
结果如下,等所有任务执行完成,只返回第一个错误
再来看下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"));
结果如下,可以看到当出现错误时会立即被捕获到
async和await
- 在Dart中使用
async
和await
关键字来实现异步编程,可以通过它们编写看起来类似于同步代码的异步代码。被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);
});
不管怎么链式调用,实际上还是会有多一层回调,而使用async
和await
,则可以像写同步代码一样处理异步操作
action() async {
try {
File file = await getImageFile("***********");
File file2 = await handleFile(file);
bool result = await saveFile(file2);
} catch (e) {
//
} finally {
//
}
}
结果如下
-
与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:StreamBuilder,Bloc
模式、flsh_redux
实现业务逻辑与UI分离、RxDart
等都离不开Stream
-
流的使用一般会涉及到四个对象:StreamController、StreamSink、StreamSubscription、Stream
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方式
-
构造方法
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
中可以使用return
或break
来中断流。注意使用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);