目录
扫描二维码关注公众号,回复:
16808258 查看本文章
4.2.2 使用Future消除回调地狱(Callback Hell)
4.2.3 使用 async/await 消除回调地狱(Callback Hell)
0 引言
Dart 是 Flutter 的基础。 Dart 作为 Flutter 应用程序的编程语言,为驱动应用运行提供了环境,同时 Dart 还支持许多核心的开发任务,例如格式化,分析和代码测试。
官方文档:Dart 语言开发文档 | Dart
学习文档:第二版序 | 《Flutter实战·第二版》 (flutterchina.club)
1 变量声明
Dart 是一个强类型语言,任何变量都是有确定类型的。
1.1 var
关键字
- var声明的变量可以赋值任意对象,一旦赋值,就不能再改变其类型
var t = "hi world";
// 下面代码在dart中会报错,因为变量t的类型已经确定为String,
// 类型一旦确定后则不能再更改其类型。
t = 1000;
1.2 dynamic
和 Object
- Dart 中所有类型都是
Object
的子类(包括Function和Null)dynamic
与Object
声明的变量都可以赋值任意对象,且后期可以改变赋值的类型Object
声明的对象只能使用Object对象自己声明
的属性与方法, 否则编译器会报错dynamic
声明的对象可以使用赋值类型自带的属性和方法,若dynamic对象使用的
属性和方法不存在,编译时不会报错,运行时会报错
dynamic t;
Object x;
t = "hi world";
x = 'Hello Object';
//下面代码没有问题
t = 1000;
x = 1000;
dynamic a;
Object b = "";
main() {
a = "";
printLengths();
}
printLengths() {
print(a.length); // 正常
print(b.length);// 报错 The getter 'length' is not defined for the class 'Object'
print(a.xx); // a是字符串,没有"xx"属性,编译时不会报错,运行时会报错
}
1.3 final
和const
- 若从未打算更改一个变量,那么使用
final
或const,它们
只能被设置一次const
变量是一个编译时常量(编译时直接替换为常量值)final
变量在第一次使用时被初始化- 被
final
或者const
修饰的变量,变量类型可以省略
//可以省略String这个类型声明
final str = "hi world";
//final String str = "hi world";
const str1 = "hi world";
//const String str1 = "hi world";
1.4 空安全
- Dart 引入了空安全,定义变量时我们可以指定变量是可空还是不可空
- 不可空变量,必须在定义时赋值,或者使用late关键字,稍后赋值
- 可空变量,通过在变量后面加一个”?“符号来声明
- 对于可空变量,在使用前必须使用if(变量!=null)判空
- 对于可空变量,某些情况下即使我们赋值了,预处理器仍然有可能识别不出,需要显式申明(通过在变量后面加一个”!“符号)告诉预处理器它已经不是null了
int i = 8; //默认为不可空,必须在定义时初始化。
int? j; // 定义为可空类型,对于可空变量,我们在使用前必须判空。
// 如果我们预期变量不能为空,但在定义时不能确定其初始值,则可以加上late关键字,
// 表示会稍后初始化,但是在正式使用它之前必须得保证初始化过了,否则会报错
late int k;
k=9;
class Test{
int? i;
Function? fun;
say(){
if(i!=null) {
print(i! * 8); //因为已经判过空,所以能走到这 i 必不为null,如果没有显式申明,则 IDE 会报错
}
if(fun!=null){
fun!(); // 同上
}
}
}
2 函数
Dart是一种真正的面向对象的语言,所以即使是函数也是对象,并且有一个类型Function。这意味着函数可以赋值给变量或作为参数传递给其他函数,这是函数式编程的典型特征。
2.1 函数声明
- Dart函数声明如果没有显式声明返回值类型,则默认返回值类型
类型是
dynamic
typedef bool CALLBACK();
//不指定返回类型,此时默认为dynamic,不是bool
isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
//void 表示没有返回值
void test(CALLBACK cb){
print(cb());
}
//报错,isNoble不是bool类型
test(isNoble);
2.2 函数作为变量
var say = (str){
print(str);
};
say("hi world");
2.3 函数作为参数传递
- 不能同时使用可选的位置参数和可选的命名参数
//定义函数execute,它的参数类型为函数
void execute(var callback) {
callback(); //执行传入的函数
}
//调用execute,将箭头函数作为参数传递
execute(() => print("xxx"))
2.3.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.3.2 可选的命名参数
- 定义函数时,使用{param1, param2, …},放在参数列表的最后面,用于指定命名参数
- 调用函数时,可以使用指定命名参数。例如:
paramName: value
//定义函数
void enableFlags({bool bold, bool hidden}) {
// ...
}
//调用函数
enableFlags(bold: true, hidden: false);
3 mixin
- Dart 是不支持多继承的,但是它支持 mixin
- mixin 可以 “组合” 多个类:定义几个 mixin,通过 with 关键字将它们组合成不同的类
- 如果多个mixin 中有同名方法,with 时,会默认使用最后面的 mixin 的方法
- mixin 方法中可以通过 super 关键字调用之前 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{}
4 异步支持
- 同步函数:该函数被调用时不会立即返回,直到该函数所要做的事情全都做完了才返回。比如在银行排队办理业务,要等到前面一个人办完才能到下一个。(一步一步做)
- 异步函数:该函数会立即返回,尽管该函数规定的操作任务还没有完成。比如一个人边吃饭,边看手机。(同时做)
- Dart类库有非常多的返回
Future
或者Stream
对象的函数,这些函数被称为异步函数- Dart支持
async
和await
关键词,用法和功能同JavaScript中的是一样的
4.1 Future
Future
与JavaScript中的Promise
非常相似,是用于处理异步操作的,异步处理成功了就执行成功的操作,异步处理失败了就捕获错误或者停止后续操作- 一个Future只会对应一个结果,要么成功,要么失败
Future
的所有API的返回值仍然是一个Future
对象
4.1.1 Future.then
- 执行耗时任务,比如一次网络请求时,在
then
中接收异步处理成功的结果
/* 使用Future.delayed 创建了一个延时任务,2秒后返回结果字符串"hi world!",
然后在then中接收异步结果并打印结果,代码如下:
*/
Future.delayed(Duration(seconds: 2),(){
return "hi world!";
}).then((data){
//执行成功会走到这里
print(data);
});
4.1.2 Future.catchError
- 如果异步任务发生错误,可以在
catchError
中捕获错误then
方法还有一个可选参数onError
,也可以用它来捕获异常
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print("success");
}).catchError((e){
//执行失败会走到这里
print(e);
});
Future.delayed(Duration(seconds: 2), () {
//return "hi world!";
throw AssertionError("Error");
}).then((data) {
//执行成功会走到这里
print("success");
}, onError: (e) {
//执行失败会走到这里
print(e);
});
4.1.3 Future.whenComplete
- 遇到无论异步任务执行成功或失败都需要做一些事的场景时,用
whenComplete处理
Future.delayed(Duration(seconds: 2),(){
//return "hi world!";
throw AssertionError("Error");
}).then((data){
//执行成功会走到这里
print(data);
}).catchError((e){
//执行失败会走到这里
print(e);
}).whenComplete((){
//无论成功或失败都会走到这里
});
4.1.4 Future.wait
- 接受一个
Future
数组参数,只有数组中所有Future
都执行成功后,才会触发then
的成功回调,只要有一个Future
执行失败,就会触发错误回调
//执行下面代码,4秒后你会在控制台中看到“hello world”
Future.wait([
// 2秒后返回结果
Future.delayed(Duration(seconds: 2), () {
return "hello";
}),
// 4秒后返回结果
Future.delayed(Duration(seconds: 4), () {
return " world";
})
]).then((results){
print(results[0]+results[1]);
}).catchError((e){
print(e);
});
4.2 async/await
- Dart中的
async/await
和JavaScript中的async/await
功能是一样的:异步任务串行化。
4.2.1 回调地狱(Callback Hell)
- 当大量异步任务依赖其他异步任务的结果时,出现
Future.then
回调中套回调的情况,过多的嵌套会导致的代码可读性下降以及出错率提高,并且非常难维护,这个问题被形象的称为回调地狱(Callback Hell)。
/*
现在有个需求场景是用户先登录,登录成功后会获得用户ID,
然后通过用户ID,再去请求用户个人信息,获取到用户个人信息后,
为了使用方便,我们需要将其缓存在本地文件系统
*/
//先分别定义各个异步任务
Future<String> login(String userName, String pwd){
...
//用户登录
};
Future<String> getUserInfo(String id){
...
//获取用户信息
};
Future saveUserInfo(String userInfo){
...
// 保存用户信息
};
//调用这些异步任务,实现需求
login("alice","******").then((id){
//登录成功后,通过id获取用户信息
getUserInfo(id).then((userInfo){
//获取用户信息后保存
saveUserInfo(userInfo).then((){
//保存用户信息,接下来执行其他操作
...
});
});
})
4.2.2 使用Future消除回调地狱(Callback Hell)
Future
的所有API的返回值仍然是一个Future
对象,所以可以很方便的进行链式调用- 如果在then 中返回的是一个
Future
的话,该future
会执行,执行结束后会触发后面的then
回调,这样依次向下,就避免了层层嵌套
login("alice","******").then((id){
return getUserInfo(id);
}).then((userInfo){
return saveUserInfo(userInfo);
}).then((e){
//执行接下来的操作
}).catchError((e){
//错误处理
print(e);
});
4.2.3 使用 async/await 消除回调地狱(Callback Hell)
async
用来表示函数是异步的,定义的函数会返回一个Future
对象,可以使用 then 方法添加回调函数。
await
后面是一个Future
,表示等待该异步任务完成,异步完成后才会往下走;await
必须出现在async
函数内部。可以通过
async/await
将一个异步流用同步的代码表示出来为了保持代码的健壮性使用
async/await
的时候,使用try catch来处理错误
async/await
只是一个语法糖,编译器最终都会将其转化为一个 Future的调用链
task() async {
try{
String id = await login("alice","******");
String userInfo = await getUserInfo(id);
await saveUserInfo(userInfo);
//执行接下来的操作
} catch(e){
//错误处理
print(e);
}
}
4.3 Stream
Stream
可以接收多个异步操作的结果(成功或失败)Stream
常用于会多次读取数据的异步任务场景,如网络内容下载、文件读写等。
Stream.fromFutures([
// 1秒后返回结果
Future.delayed(Duration(seconds: 1), () {
return "hello 1";
}),
// 2秒后抛出一个异常
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