变量声明

关键字 var

经过 var 声明一个变量,假设这个变量在声明时赋值了,此刻类型就现已确认了,再次赋值时不能更改其他类型。

var str = "hello dart";
// 由于声明时现已赋值为String,所以不能更改类型
str = 3; // 编译器会报错
str = "nihao"; //再次赋值只能为String类型

由于 Dart 本身是一个强类型言语,任何变量都是有确认类型的,在 Dart 中,当用var声明一个变量后,Dart 在编译时会根据第一次赋值数据的类型来揣度其类型,编译完毕后其类型就现已被确认。

关键字 dynamic和Object

Object是Dart一切目标的基类,便是说在 Dart 中一切类型都是Object的子类(包括Function和Null),所以任何类型的数据都能够赋值给Object声明的目标。 dynamicObject声明的变量都能够赋值恣意目标,且后期能够改动赋值的类型,这和 var 是不同的,如:

dynamic str = "hi dart!";
str = 4;
​
Object src = 34.32;
src = "nihao";

这样赋值时没有任何问题的,这时分有人会问,已然用 dynamic 和 object 都能够让变量赋值任何类型,那么这两个有什么差异呢? dynamicObject 不同的是 dynamic 声明的目标编译器会供给一切或许的组合,而 Object 声明的目标只能运用 Object 的特点与办法, 不然编译器会报错,如:

dynamic a = "hi dart!";
​
Object b = "nihao";
​
// 正常打印  值为8
print(a.length);
// 报错
print(b.length);

用Object润饰的变量b,不能运用length特点,由于Object目标并没有这个特点,而用dynamic润饰的变量a,赋予了String类型的特点和办法,所以能够运用length特点。

但是这儿有个点需求留意: dynamic润饰的变量a,运用a.length没有报错,而且能正常打印值,是由于 a 被赋值为String 类型,而String类型有length这个特点,但是假设咱们把length改成其他String类型没有的特点,比方随便写一个不存在的特点:

print(a.ccccc); //  a是字符串,没有"ccccc"特点,编译时不会报错,运行时会报错

这样写在编译期不会报错,但运行时会报错,这一点要留意。

关键字 final和const

final 和const 都是润饰一个不可变的变量,一个final变量只能被设置一次,两者差异在于:const 变量是一个编译时常量(编译时直接替换为常量值),final变量在第一次运用时被初始化。被final或许const润饰的变量,变量类型能够省掉,如:

//能够省掉String这个类型声明
final str = "hi dart";
//final String str = "hi dart"; 
const str1 = "hi dart";
//const String str1 = "hi dart";

空安全(null-safety)

Dart 中一切都是目标,这意味着假设咱们界说一个数字,在初始化它之前假设咱们运用了它,假设没有某种检查机制,则不会报错,比方:

test() {
 int i; 
 print(i*8);
}

在 Dart 引入空安全之前,上面代码在履行前不会报错,但会触发一个运行时过错,原因是 i 的值为 null 。但现在有了空安全,则界说变量时咱们能够指定变量是可空仍是不可空。

一文带你全盘把握Dart根本语法

由于空安全机制,将运行时的过错提早到编译期,这样在咱们对这个变量操作时,提早发现这个变量是否为空,从而避免运行时崩溃。

int i = 8; // 默许不为空,运用前有必要初始化
int? j; // 界说为可空类型,关于可空变量,咱们在运用前有必要判空。
​
// 假设咱们预期变量不能为空,但在界说时不能确认其初始值,则能够加上late关键字,
// 表明会稍后初始化,但是在正式运用它之前有必要得保证初始化过了,不然会报错
late int k;
k = 9;

假设一个变量咱们界说为可空类型,在某些状况下即便咱们给它赋值过了,但是预处理器仍然有或许辨认不出,这时咱们就要显式(经过在变量后边加一个“!”符号)告诉预处理器它现已不是null了,比方:

class Test {
 int? i;
 Function? fun;
​
 say() {
  if (i != null) {
   print(i! * 8); // 由于现已判过空,所以能走到这 i 必不为null,假设没有显式声明,则 IDE 会报错
   }
​
  if (fun != null) {
   fun!(); // 同上
   }
  }
}

上面中假设函数变量可空时,调用的时分能够用语法糖:

fun!.call(); // fun不为空时,调用call

内置类型

num

咱们都知道,在Dart中,没有根本数据类型,一切的数据类型都是class,num便是其中一种表明数值的class,咱们所运用的int和double都承继自num,在平时运用过程中,只需留意不要超过int的最大字节数就能够了。int的最大字节数和和编译环境有关,32位机器上运行时,int最大是4个字节,64位机器上运行时,int最大是8个字节。

一文带你全盘把握Dart根本语法

一文带你全盘把握Dart根本语法

int j = 5;
print(j.bitLength);  // 回来存储此整数所需的最小位数。5 -> 101  最小是3位

String

  1. 单引号声明

声明一个String类型字符串,既能够用双引号(""),也能够用单引号('')。

  1. 格式化

在java中,咱们运用String做字符串拼接时有三种办法:+ 、 StringBuilder 、String.format()。运用最多的根本便是前两种了,但是在dart中,咱们拼接两个字符串时能够运用”$“符号,后边跟上变量名,假设变量名后边还有英文,咱们能够运用{}包括变量名,和英文分割开。

String a = "hello";
String b = 'dart';
print("$a $b"); // hello dart
print("${a}nihao$b");  // hellonihaodart
  1. 双引号转义
 String c = ""test""; // "test"
  1. 三引号("""

假设进行多行字符串声明时,能够运用"""

  String desc = """
  原生运用程序是指某一个移动平台(比方iOS或安卓)所特有的运用,
  运用相应平台支撑的开发工具和言语,并直接调用系统供给的SDK API。
  比方Android原生运用便是指运用Java或Kotlin言语直接调用Android SDK开发的运用程序;
  而iOS原生运用便是指经过Objective-CSwift言语直接调用iOS SDK开发的运用程序。
  """;

List 和 Map

在Dart 中数组等于列表,声明一个调集和map能够运用:

 var list = []; 
 var map = {};

以上声明相当于java中的:List list = new List()Map map = new Map() list中的元素和map中的key-value类型都是dynamic。由于Dart中没有根本数据类型,所以在list中,能够给元素赋值恣意类型,而他们基类都是Object,map同理。

假设想指定list中的元素类型,能够运用:

List<String> list = []Map<int, String> map = {}

假设想取调集里边的元素,经过list[0]来取下标为0的元素。

  1. 遍历数组 (for-i模板和for-each模板)

iter: for (var o in list) {}

itar: for (var i = 0; i < list.length; ++i) {}

  1. 遍历map:迭代器的遍历
var keys = map.keys;
for (var element in keys) {
}

操作符

类型断定操作符

  1. 类型转换

运用as关键字,后边跟上要转换的类型

num j = 1;
int a = j as int;
  1. 类型判别

判别一个变量是否归于这个类型,运用is关键字,相当于java中的instantof,判别不归于该类型,运用is!

Object i = 2;
if(i is int) {
  print("$i 归于 int"); // 2 归于 int
}
Object j = "2";
if(j is! int) {
  print("$j 不归于 int"); // 2 不归于 int
}

赋值操作符

前面现已讲过Dart中的空安全机制了,dart中默许变量不为空,假设声明时不赋初值,则运用String? s;来阐明该变量可空。

  1. ??

假设左面表达式的值不为null,则直接回来自己的值,不然回来右边表达式的值。

k = null;
var v = k ?? "789";
print(v);  // k为null,回来789,假设k不为null,例如:k = 123,则回来123
  1. ??=

左面值不为null时,直接回来自己的值,不然回来右边表达式的值。

  String? k = "xx";
  String? k1 = null;
  k ??= "123";  // k 不为null,这句表达式无效
  k1 ??= "456";
  print(k); // k = xx;
  print(k1); // k1 = 456;

级联操作符..

void main() {
  Builder()..a()..b();
}
class Builder {
  void a() {}
  void b() {}
}

安全操作符?.

  String? str;
  print(str?.length);

函数

Dart是一种真实的面向目标的言语,所以即便是函数也是目标而且有一个类型Function。这意味着函数能够赋值给变量或许作为参数传递给其他函数,这便是函数式编程的典型特征。

函数声明

bool isNoble(int atomicNumber) {
    return _nobleGases[atomicNumber] != null;
}

Dart函数声明假设没有显式声明回来值类型时会默许当做dynamic处理,留意,函数回来值没有类型揣度:

一文带你全盘把握Dart根本语法
关于只包括一个表达式的函数,能够运用简写语法:

 bool isTrue() => true;

函数作为变量

var say = (str) {
    print(str); // 打印:hi dart!
};
​
say("hi dart!"); 
​

函数作为参数传递

void execute(var callback) {
  callback();
}
execute(() => print("xxx"))

上面的比方,便是把一个() => print("xxx")函数当作参数传递给callback,然后execute里履行callback()函数,也便是履行了传进去的那个函数,打印:”xxx”。

1. 可选的方位参数

包装一组函数参数,用[]标记为可选的方位参数,并放在参数列表的最终边:

String say(String from, String msg, [String? device]) {
 var result = '$from says $msg';
 if (device != null) {
  result = '$result with a $device';
  }
 return result;
}

下面是一个不带可选参数调用这个函数的比方:

say('Bob', 'Howdy'); //成果是: Bob says Howdy

下面是用第三个参数调用这个函数的比方:

say('Bob', 'Howdy', 'smoke signal'); //成果是:Bob says Howdy with a smoke signal
2. 可选的命名参数

界说函数时,运用{param1, param2, …},放在参数列表的最终边,用于指定命名参数。例如:

//设置[bold]和[hidden]标志
void enableFlags({bool bold, bool hidden}) {
  // ... 
}

调用函数时,能够运用指定命名参数。例如:paramName: value

enableFlags(bold: true, hidden: false);

可选命名参数在Flutter中运用十分多。留意,不能同时运用可选的方位参数和可选的命名参数

类的私有化

运用_+类名/变量名,表明该类或许类的成员特点是私有的,只能在当前文件拜访,外部无法拜访,相当于java中的private

class _Point {
	int? _x;
	int? _y;
	String? _name;
}

类的结构办法

  1. 命名结构办法

在dart中,没有像java中的结构办法重载,但是在咱们实践开发的场景中,的确会遇到重载的需求,dart供给了命名结构办法,处理了没有结构办法重载的问题。

class _Point {
	int? _x;
	int? _y;
	String? _name;
	_Point(this._x, this._y); //结构办法
	// 命名结构办法
	_Point.setY(int _y) {
		this._y = _y;
	}
	// 命名结构办法
	_Point.setName(String _name) {
		this._name = _name;
		print(_name);
	}
}
  1. 参数初始化列表

从一个外部传递的参数,来初始化类的特点。

  _Point.fromMap(Map map) : _x = map["x"], _y = map["y"];

这句代码的意思很直白,从map中取出k"x"的值赋值给_x,取出k"y"的值赋值给_y

  1. 重定向结构办法
class View {
  View(int context, int attr);
  // 重定向结构办法
  View.a(int context):this(context, 0);
}
  1. 常量结构办法

在结构办法前面加上const关键字,就阐明该结构办法是常量结构办法。

一文带你全盘把握Dart根本语法
这样写编译器会报错:不允许在const的结构办法里传入非final的参数。咱们需求在参数前面加上final

class ImmutabelPoint {
  final int x;
  final int y;
  const ImmutabelPoint(this.x, this.y);
}

假设运用const来界说了结构办法,在运用结构办法创立目标时,能够这样来用:

void main() {
	var p1 =  const ImmutabelPoint(1, 1);
	var p2 =  const ImmutabelPoint(1, 1);
	print(p1.hashCode == p2.hashCode);  // true
	print(p1 == p2);  //true
}

咱们知道,在java中,经过new的办法来创立目标,每次创立会拓荒一块新的内存,在dart中,相同经过new的办法创立目标的话,和一般目标没有差异。而经过const来创立多个目标,而且传递参数也相同,这几个目标其实是同一个目标,又叫编译期常量目标。

在上面的示例中,p1和p2是同一个目标。

  1. 工厂结构办法

运用factory声明的结构办法,叫做工厂结构办法。工厂结构办法,有必要回来一个实例目标。

class Manager {
  factory Manager.get() {
    return Child();  // 回来子类实例目标
   // return Manager();  // 回来本身实例目标
  }
  Manager(); // 有必要供给默许结构办法,不然编译器报错
}
class Child extends Manager {
}
void main() {
	Manager.get();
}
class Manager {
  static Manager? _instance;
  factory Manager.getInstance() {
   return _instance ??= Manager._newInstance();
  }
  //私有的
  Manager._newInstance();
}

上面便是一个单例形式,在外部文件中只能经过Manager.getInstance()来获取Manager实例。

操作符重载

class Point {
  int? _x;
  int? _y;
  Point operator +(Point other) {
    var point = Point();
    point._x = _x! + other._x!;
    return point;
  }
}
void main() {
  var p1 = Point();
  var p2 = Point();
  p1._x = 10;
  p2._x = 40;
  var p3 = p1 + p2;
  print(p3._x); // 成果:50
}

笼统类

和java的差异在于:办法前面不需求加abstract,而且能够写办法体。

abstract class Test {
  void test(); // 笼统办法,不需求在办法前面声明abstract
  void test1() {
  }
}

接口

和java不同,dart中没有interface关键字,Dart中每个类都隐式的界说了一个包括一切实例成员的接口,而且这个类完结了这个接口,假设你想创立类A来支撑类B的办法,而不想承继B的完结,则类A应该完结B的接口。

class Listener {
  void onSuccess(){}
  void onFailure(){}
}
class MyListener implements Listener {
  @override
  void onFailure() {
    // TODO: implement onFailure
  }
  @override
  void onSuccess() {
    // TODO: implement onSuccess
  }
}

call

在dart中,还有一种骚操作,目标当函数调用。假设Dart类完结了call()办法,则目标能够当作办法来调用。

class Test2 {
  void call() {
    print("调用了call办法");
  }
}
void main() {
	var test2 = Test2();
	test2(); // 这句代码就调用了Test2类中的call办法,打印:调用了call办法
}

混合mixin

Dart不支撑多承继,但是他支撑mixin,简略来讲 mixin 能够 “组合” 多个类,咱们经过一个比方来理解。 界说一个 Person 类,完结吃饭、说话、走路和写代码功用,同时界说一个 Dog 类,完结吃饭、和走路功用:

class Person {
 say() {
  print('say');
  }
}
​
mixin Eat {
 eat() {
  print('eat');
  }
}
​
mixin Walk {
 walk() {
  print('walk');
  }
}
​
mixin Code {
 code() {
  print('key');
  }
}
​
class Dog with Eat, Walk{}
class Man extends Person with Eat, Walk, Code{}

咱们界说了几个 mixin,然后经过 with 关键字将它们组合成不同的类。有一点需求留意假设多个mixin 中有同名办法,with 时,会默许运用最终边的 mixin 的,mixin 办法中能够经过 super 关键字调用之前 mixin 或类中的办法。

Dart异步编程

isolate机制

Dart是基于单线程模型的言语,但是在开发当中咱们经常会进行耗时操作比方网络恳求,这种耗时操作会堵塞咱们的代码,所以在Dart也有并发机制,叫:isolate。 APP的发动入口main函数便是一个类似Android主线程的一个主isolate,和java的Thread不同的是,Dart中的isolate无法同享内存。

  1. isolate的双向通讯机制
void main() {
  var receivePort = ReceivePort();
  Isolate.spawn(entryPoint, receivePort.sendPort);
  receivePort.listen((message) {
    if (message is SendPort) {
      message.send("主  ------>  子"); // 主isolate发送音讯到子isolate
    } else {
      print(message);  // 这儿接收子isolate发送的音讯:子  ------>  主
    }
  });
}
void entryPoint(SendPort sendPort) {
  var receivePort = ReceivePort();
  var sendPort2 = receivePort.sendPort;
  sendPort.send(sendPort2);
  sendPort.send("子  ------>  主");  // 子isolate发送音讯到主isolate
  receivePort.listen((message) {
    print(message); // 这儿接收主isolate发送的音讯:主  ------>  子
  });
}

解析一下上面的流程:

  1. 在dart中,isolate虽然不像java中的线程相同,同享同一块内存,但又不能划在进程等级,由于进程量级是很重的,其实更像是dart中的线程,只不过是内存隔离算了。
  2. 已然是线程,那么就会有主线程和子线程,线程之间音讯是能够彼此通讯的,留意:这儿不触及线程同步问题(由于内存隔离), 上面的代码中,main函数所处的线程便是主线程,entryPoint函数所处的线程便是子线程。
  3. 在主线程中,创立一个音讯接收器receivePort,然后将音讯接收器当中的发送器receivePort.sendPort发给子线程,在子线程中,运用主线程发过来的sendPort进行send发送,就能够在主线程进行listen监听音讯,接收到子线程的音讯并打印。
  4. 在子线程中,相同也能够创立一个音讯接收器receivePort,然后将音讯接收器当中的发送器sendPort.send(sendPort2)发送给主线程,相同能够在 主线程监听,判别message假设是SendPort,就能够在主线程发送音讯给子线程了,子线程监听到音讯后就会打印。
    一文带你全盘把握Dart根本语法

使命队列

void main() {
  var receivePort = ReceivePort();
  receivePort.listen((message) {
    print(message);
  });
  // 在微使命队列中提交一个使命
  Future.microtask(() {
      print("微使命履行1");
  });
  receivePort.sendPort.send("发送音讯给音讯接收器1");
  Future.microtask(() {
    print("微使命履行2");
  });
  receivePort.sendPort.send("发送音讯给音讯接收器2");
  Future.microtask(() {
    print("微使命履行3");
  });
  receivePort.sendPort.send("发送音讯给音讯接收器3");
}

上面的输出成果是:

微使命履行1
微使命履行2
微使命履行3
发送音讯给音讯接收器1
发送音讯给音讯接收器2
发送音讯给音讯接收器3

这就证明微使命优先级是高于一般使命的,无论是先提交微使命仍是一般使命,微使命优先履行。

那么咱们会有一个疑问:微使命会插队吗?看下面的代码:

void main() {
  var receivePort = ReceivePort();
  receivePort.listen((message) {
    print(message);
    Future.microtask(() {
      print("敞开微使命");
    });
  });
  receivePort.sendPort.send("发送音讯给音讯接收器1");
  receivePort.sendPort.send("发送音讯给音讯接收器2");
  receivePort.sendPort.send("发送音讯给音讯接收器3");
}

首要提交三个一般使命,在接收到一般使命后,插入微使命,此刻微使命会在这儿插队履行吗?答案是:会插队

发送音讯给音讯接收器1
敞开微使命
发送音讯给音讯接收器2
敞开微使命
发送音讯给音讯接收器3
敞开微使命

看到这样的打印,就明白了微使命是会插队履行的。

假设:我现在在main方面最终加入sleep休眠,会不会影响listen回调的时机?

是会影响的,一切的使命都要等sleep休眠完毕,再履行,这就再次证明了Dart是单线程的定论。

一文带你全盘把握Dart根本语法

Future

Future与JavaScript中的Promise十分类似,表明一个异步操作的最终完结(或失利)及其成果值的表明。简略来说,它便是用于处理异步操作的,异步处理成功了就履行成功的操作,异步处理失利了就捕获过错或许中止后续操作。一个Future只会对应一个成果,要么成功,要么失利。 由于本身功用较多,这儿咱们只介绍其常用的API及特性。还有,请记住,Future的一切API的回来值仍然是一个Future目标,所以能够很便利的进行链式调用。

1)Future.then

为了便利示例,在本例中咱们运用Future.delayed 创立了一个延时使命(实践场景会是一个真实的耗时使命,比方一次网络恳求),即2秒后回来成果字符串”hi world!”,然后咱们在then中接收异步成果并打印成果,代码如下:

 Future.delayed(Duration(seconds: 2), () {
  return "hi world";
  }).then((value) => print(value));
2)Future.catchError

假设异步使命产生过错,咱们能够在catchError中捕获过错,咱们将上面示例改为:

Future.delayed(Duration(seconds: 2),(){
  //return "hi world!";
  throw AssertionError("Error"); 
}).then((data){
  //履行成功会走到这儿 
  print("success");
}).catchError((e){
  //履行失利会走到这儿 
  print(e);
});

在本示例中,咱们在异步使命中抛出了一个反常,then的回调函数将不会被履行,取而代之的是 catchError回调函数将被调用;但是,并不是只需 catchError回调才能捕获过错,then办法还有一个可选参数onError,咱们也能够用它来捕获反常:

Future.delayed(Duration(seconds: 2), () {
    //return "hi world!";
    throw AssertionError("Error");
}).then((data) {
    print("success");
}, onError: (e) {
    print(e);
});
3)Future.whenComplete

有些时分,咱们会遇到无论异步使命履行成功或失利都需求做一些事的场景,比方:在网络恳求前弹出加载对话框,在恳求完毕后封闭对话框,这种场景,有两种办法:

  1. 别离在thencatch中封闭一下对话框
  2. 运用FuturewhenComplete回调

咱们将上面示例改一下:

Future.delayed(Duration(seconds: 2),(){
    //return "hi world!";
    throw AssertionError("Error");
}).then((data){
    //履行成功会走到这儿 
    print(data);
}).catchError((e){
    //履行失利会走到这儿  
    print(e);
}).whenComplete((){
    //无论成功或失利都会走到这儿
});
4)Future.wait

有些时分,咱们需求等待多个异步使命都履行完毕后才进行一些操作,比方咱们有一个界面,需求先别离从两个网络接口获取数据,获取成功后,咱们需求将两个接口数据进行特定的处理后再显现到UI界面上,应该怎么做?答案是Future.wait,它承受一个Future数组参数,只需数组中一切Future都履行成功后,才会触发then的成功回调,只需有一个Future履行失利,就会触发过错回调。下面,咱们经过模拟Future.delayed 来模拟两个数据获取的异步使命,等两个异步使命都履行成功时,将两个异步使命的成果拼接打印出来,代码如下:

Future.wait([
    // 2秒后回来成果 
    Future.delayed(Duration(seconds: 2), () {
        return "hello";
    }),
    // 4秒后回来成果 
    Future.delayed(Duration(seconds: 4), () {
        return "world";
    })
]).then((result) {
    print(result[0] + result[1]);
}).catchError((e) {
    print(e);
});

履行上面代码,4秒后你会在控制台中看到“hello world”。

2. async/await

Dart中的async/await的意思便是:异步使命串行化,能够避免咱们在运用过程中遇到的回调阴间问题。 先来看看什么是回调阴间:

1)回调阴间(Callback Hell)

假设代码中有很多异步逻辑,而且呈现很多异步使命依赖其他异步使命的成果时,必然会呈现Future.then回调中套回调的状况。举个比方:现在有个需求场景是用户先登录,登录成功后会获得用户ID,然后经过用户ID,再去恳求用户个人信息,获取到用户个人信息后,为了运用便利,咱们需求将其缓存在本地文件系统,代码如下:

//先别离界说各个异步使命
Future<String>? login(String userName, String pwd){
    ...
  //用户登录
};
Future<String>? getUserInfo(String id){
    ...
  //获取用户信息 
};
Future? saveUserInfo(String userInfo){
    ...
    // 保存用户信息 
}; 

接下来,履行整个使命流:

login("admin", "xxx")?.then((id) {
    getUserInfo(id)?.then((userInfo) {
        saveUserInfo(userInfo)?.then((value) {
            // ...
        });
    });
});

能够感受一下,假设业务逻辑中有很多异步依赖的状况,将会呈现上面这种在回调里边套回调的状况,过多的嵌套会导致的代码可读性下降以及出错率提高,而且十分难维护,这个问题被形象的称为回调阴间(Callback Hell) 。回调阴间问题在之前 JavaScript 中十分突出,也是 JavaScript 被吐槽最多的点,但随着 ECMAScript 标准发布后,这个问题得到了十分好的处理,而处理回调阴间的两大神器正是 ECMAScript6 引入了Promise,以及ECMAScript7 中引入的async/await。 而在 Dart 中几乎是完全平移了 JavaScript 中的这两者:Future相当于Promise,而async/await连名字都没改。接下来咱们看看经过Futureasync/await怎么消除上面示例中的嵌套问题。

2)消除回调阴间

消除回调阴间主要有两种办法:

一、运用Future消除Callback Hell

login("alice","******").then((id){
    return getUserInfo(id);
}).then((userInfo){
  return saveUserInfo(userInfo);
}).then((e){
  //履行接下来的操作 
}).catchError((e){
 //过错处理 
 print(e);
});

正如上文所述, “Future 的一切API的回来值仍然是一个Future目标,所以能够很便利的进行链式调用” ,假设在then中回来的是一个Future的话,该future会履行,履行完毕后会触发后边的then回调,这样依次向下,就避免了层层嵌套。

二、运用 async/await 消除 callback hell

经过Future回调中再回来Future的办法虽然能避免层层嵌套,但是仍是有一层回调,有没有一种办法能够让咱们能够像写同步代码那样来履行异步使命而不运用回调的办法?答案是必定的,这就要运用async/await了,下面咱们先直接看代码,然后再解释,代码如下:

task() async {
    try {
        String? id = await login("admin", "xxx");
        String? userInfo = await getUserInfo(id!);
        await saveUserInfo(userInfo!);
        //履行接下来的操作 
    } catch(e) {
        //过错处理
        print(e);
    }
}
  • async用来表明函数是异步的,界说的函数会回来一个Future目标,能够运用 then办法添加回调函数。
  • await后边是一个Future,表明等待该异步使命完结,异步完结后才会往下走。
  • await有必要呈现在 async函数内部。

能够看到,咱们经过async/await将一个异步流用同步的代码表明出来了。

Stream

Stream也是用于接收异步事情数据,和 Future不同的是,它能够接收多个异步操作的成果(成功或失利)。 也便是说,在履行异步使命时,能够经过屡次触发成功或失利事情来传递成果数据或过错反常。 Stream常用于会屡次读取数据的异步使命场景,如网络内容下载、文件读写等。举个比方:

Stream.fromFutures([
    // 1秒后回来成果
    Future.delayed(Duration(seconds: 1), () {
        return "hello 1";
    }),
    // 抛出一个反常
    Future.delayed(Duration(seconds: 2),(){
        throw AssertionError("Error");
    }),
    // 3秒后回来成果
    Future.delayed(Duration(seconds: 3), () {
        return "hello 3";
    })
]).listen((data){
    print(data);
}, onError: (e){
    print(e.message);
},onDone: (){
​
});

上面的代码依次会输出:

I/flutter (17666): hello 1
I/flutter (17666): Error
I/flutter (17666): hello 3