目录

一、异步编程

1. 先搞懂:为什么要用异步?

2. 同步 vs 异步

3. 拿到异步的结果

4. async和await

二、Dart 扩展特性

1. 空安全 —— 避免「程序崩了不知道为啥」

2. 空安全在Flutter的用法:

3. 枚举

4. 扩展方法

结尾


        这篇就是Dart 语法的收尾,主要讲一下异步编程。

一、异步编程

1. 先搞懂:为什么要用异步?

        Flutter 采用单线程模型,主线程(UI 线程)负责处理用户交互、渲染界面和执行逻辑。若在主线程执行耗时操作(如网络请求、文件读写),会导致界面卡顿或无响应(ANR)。

        必须通过异步操作将耗时任务转移到后台线程,主线程继续响应用户交互,任务完成后通过回调或事件通知更新界面。

        你可以把 Flutter 想象成一个「单线程的小饭馆」:

  • 「主线程」就是唯一的「服务员」,既要给客人点菜(处理用户点击),又要上菜(渲染界面),还要洗碗(处理逻辑);
  • 如果客人点了一道「需要炖 2 小时的汤」(耗时操作:比如网络请求、读取大文件),服务员全程盯着炖汤,就没法给其他客人服务了 → 界面就会「卡住」(ANR),用户点按钮没反应、界面不刷新;
  • 「异步」就是:服务员把炖汤的活交给「后厨」(异步线程),自己继续给其他客人服务,等汤炖好了,后厨喊一声,服务员再过来端汤 → 界面不卡,用户体验好。

        核心结论:只要代码需要「等」(比如等网络数据、等文件读取),就必须用异步,否则界面会卡死。

2. 同步 vs 异步

先看「同步代码」

void main() {
  print("1. 服务员开始工作");
  
  // 模拟「炖2小时汤」:这里用sleep(同步等待,会卡住)
  // 注意:Flutter里绝对不能用sleep,这里只是举例!
  sleep(Duration(seconds: 2)); 
  print("2. 汤炖好了");
  
  print("3. 服务员继续服务其他客人");
}

        运行结果:先打印 1 → 卡 2 秒 → 打印 2 → 打印 3。

        卡 2 秒期间,服务员啥也干不了,界面会卡死。

再看「异步代码」

void main() {
  print("1. 服务员开始工作");
  
  // 模拟把炖汤交给后厨:用Future.delayed(异步等待)
  Future.delayed(Duration(seconds: 2), () {
    print("2. 汤炖好了(后厨喊服务员)");
  });
  
  print("3. 服务员继续服务其他客人");
}

        运行结果:先打印 1 → 立刻打印 3 → 等 2 秒打印 2。

        优点:服务员没闲着,界面不卡。

  • Future.delayed(时间, 回调函数):延迟指定时间后,执行回调函数里的代码;
  • 回调函数:要具体做的操作。

3. 拿到异步的结果

        异步代码是在后台运行的,它的运行结果可能不会立刻出来,那我们要怎么才能拿到?

        关键字: then()

举例说明

// 定义一个「让后厨炖汤」的函数
// Future<String>:表示后厨最终会返回「汤的名字」(字符串)
Future<String> cookSoup() {
  // 后厨开始炖汤,需要2秒
  return Future.delayed(Duration(seconds: 2), () {
    // 炖汤的结果:返回汤的名字
    return "番茄牛腩汤";
  });
}

void main() {
  print("1. 客人点单,服务员把炖汤交给后厨");
  
  // 调用异步函数:拿到「纸条(Future)」,不是汤本身
  Future<String> soupFuture = cookSoup();
  
  // then():纸条上写的「汤好后要做的事」
  soupFuture.then((String soupName) {
    // soupName就是后厨返回的结果(汤的名字)
    print("2. 汤炖好了:$soupName,端给客人");
  });
  
  print("3. 服务员继续服务其他客人");
}

运行结果

1. 客人点单,服务员把炖汤交给后厨
3. 服务员继续服务其他客人
2. 汤炖好了:番茄牛腩汤,端给客人

         万一操作失败了,就要使用关键字:catchError () 来捕获异常。

举例说明

Future<String> cookSoup() {
  return Future.delayed(Duration(seconds: 2), () {
    // 模拟炖汤失败:抛出错误
    throw Exception("汤炖糊了!");
    // return "番茄牛腩汤"; // 正常情况
  });
}

void main() {
  print("1. 客人点单");
  
  cookSoup()
      .then((soupName) {
        print("2. 汤好了:$soupName");
      })
      .catchError((error) {
        // 处理失败情况
        print("2. 出错了:$error");
      });
  
  print("3. 服务员继续干活");
}

运行结果

1. 客人点单
3. 服务员继续干活
2. 出错了:Exception: 汤炖糊了!

4. async和await

        then() 写多了会像「套娃」,因为then() 可以链式调用,但语法观感很差,所以async/await是「把异步代码写成同步样子」,看起来会很舒服。

核心:

  • 给函数加 async:告诉 Dart「这个函数里有异步操作」,比如 void main() async
  • 异步操作前加 await:告诉 Dart「等这个异步操作做完,再往下走」,只能在加了 async 的函数里用
// 异步函数:炖汤
Future<String> cookSoup() async {
  // await:等2秒,再继续(后厨炖汤)
  await Future.delayed(Duration(seconds: 2));
  // 正常返回结果
  return "番茄牛腩汤";
  // 失败就抛错:throw Exception("汤糊了");
}

// main函数加async:里面要用await
void main() async {
  print("1. 客人点单");
  
  // try/catch:处理成功/失败(替代then/catchError)
  try {
    // await:等炖汤完成,直接拿到结果(不用纸条了)
    String soupName = await cookSoup();
    print("2. 汤好了:$soupName");
  } catch (error) {
    // 失败时执行这里
    print("2. 出错了:$error");
  }
  
  print("3. 服务员继续干活");
}

    运行结果

    1. 客人点单
    (等2秒)
    2. 汤好了:番茄牛腩汤
    3. 服务员继续干活

            大家可能会觉得,这不同样等了2秒吗,哪来的异步?注意看await的执行流程

            await 的执行机制:暂停当前函数,但不阻塞线程

    • 当前函数(如 main)会被“挂起” :代码执行到 await 处时,函数暂停,控制权返回给事件循环。
    • 线程不阻塞 :事件循环可以处理其他任务

            因为刚才那段代码中就一个任务,所以在这里它等了2秒。

            接下来这段代码应该能让大家更清晰的感觉到await的用法

    // 模拟耗时较长的异步任务(炖汤,2秒)
    Future<String> cookSoup() async {
      print("开始炖汤(需要2秒)");
      await Future.delayed(Duration(seconds: 2));
      return "番茄牛腩汤";
    }
    
    // 模拟耗时较短的异步任务(打扫卫生,1秒)
    Future<void> cleanTable() async {
      print("开始打扫卫生(需要1秒)");
      await Future.delayed(Duration(seconds: 1));
      print("卫生打扫完成");
    }
    
    void main() async {
      print("1. 服务员开始工作");
    
      // 启动打扫卫生任务(异步执行,不等待)
      cleanTable();
    
      // await 等待炖汤完成(挂起 main 函数,但线程不阻塞)
      print("2. 开始等汤...");
      String soup = await cookSoup();
      print("3. 汤好了:$soup");
    
      print("4. 服务员继续其他工作");
    }
    

    运行结果

    1. 服务员开始工作
    开始打扫卫生(需要1秒)
    2. 开始等汤...
    开始炖汤(需要2秒)
    卫生打扫完成
    3. 汤好了:番茄牛腩汤
    4. 服务员继续其他工作

            在打扫卫生的时候开始等汤,在炖汤的时候,卫生打扫完成了,所以使用await当前函数会被“挂起”,但还可以处理其他任务,并不是同步执行。

    注:没加async,不能用await 

    二、Dart 扩展特性

            这部分不用懂原理,记「固定写法」就行,学会了能少踩坑、少写重复代码。

    1. 空安全 —— 避免「程序崩了不知道为啥」

            最常见的崩溃:「空指针异常」(比如用一个值为 null 的变量)。

            Dart 的空安全就是「提前防错」。

    运算符 写法 作用 例子
    ?. 变量?. 属性 变量不为空才取属性,为空就返回 null str?.length
    ?? 变量 ?? 默认值 变量为空就用默认值,不为空就用自己的值 str ?? "默认文字"
    void main() {
      String? str = null; // ?表示这个变量可以为空
      
      // ?.:str是空,所以返回null
      int? len = str?.length;
      print(len); // 打印:null
      
      // ??:str是空,用默认值
      String newStr = str ?? "我是默认文字";
      print(newStr); // 打印:我是默认文字
    }

    2. 空安全在Flutter的用法:

            后端返回的数据可能少字段,用这两个运算符就不会崩:

    // 模拟后端返回的数据(name字段为空)
    Map<String, dynamic> user = {"name": null};
    
    // 安全解析:为空就用「匿名用户」
    String userName = user["name"] ?? "匿名用户";
    print(userName); // 打印:匿名用户

    3. 枚举

            表示状态用枚举,用字符串写状态(比如 "加载中" "成功" "失败"),容易拼错(比如写成 "加载zhong" "成攻"),代码运行时才报错;用枚举的话,编辑器会提示可选值,写错了直接标红,根本运行不了。枚举是「固定的选项」,只能选里面的,不会错。

    // 第一步:定义枚举(给“选择题”定选项)
    // 规则:枚举名首字母大写,选项小写(比如PageState、loading)
    enum PageState { loading, success, error }
    
    void main() {
      // 第二步:使用枚举(选一个选项)
      // 只能写PageState.loading/success/error,写别的会报错
      PageState nowState = PageState.loading;
      
      // 第三步:判断枚举(用if/switch都行,新手先学if)
      if (nowState == PageState.loading) {
        print("页面正在转圈圈加载");
      } else if (nowState == PageState.success) {
        print("数据加载好了,显示内容");
      } else if (nowState == PageState.error) {
        print("加载失败,点一下重试吧");
      }
    }

    4. 扩展方法

            举个例子,比如 Dart 的 String 类(字符串)本身只有toUpperCase()(全大写)、length(长度)等功能,没有 “首字母大写” 的功能;用扩展方法就能给 String 加这个功能,以后所有字符串都能直接用。

    // 第一步:定义扩展(给String加“首字母大写”技能)
    // 规则:extension 自定义名字 on 要扩展的类(比如MyString on String)
    extension MyStringHelper on String {
      // 第二步:写自定义功能(方法名自己取,比如bigFirst)
      String bigFirst() {
        // this代表当前字符串(比如调用"flutter".bigFirst(),this就是"flutter")
        // 逻辑:取第一个字符转大写 + 剩下的字符
        return this.substring(0,1).toUpperCase() + this.substring(1);
      }
    }
    
    void main() {
      // 第三步:使用扩展功能(直接用字符串.方法名)
      String text1 = "flutter";
      print(text1.bigFirst()); // 打印:Flutter
      
      String text2 = "hello";
      print(text2.bigFirst()); // 打印:Hello
    }

    结尾

            这些基础知识点看着零散,但都是 Flutter 开发里「能直接落地」的核心技巧 —— 不用急着一次性全记住,先把异步 + setState、空安全这两个最常用的点敲熟,再慢慢把枚举、扩展方法加进去。

            编程的核心从来不是背语法,而是「用起来」:哪怕今天只学会了给异步请求加 try/catch,或者用?. 避免了一次崩溃,都是实实在在的进步。

             欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

    Logo

    有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

    更多推荐