零基础鸿蒙应用开发第三十一节:await简化异步编程与Promise进阶并发

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 掌握 async/await 语法糖的核心用法,将 Promise 异步代码“同步化”书写,彻底摆脱链式调用冗余;
  2. 熟练使用 try/catch/finally 处理异步错误,覆盖同步+异步场景的统一错误捕获;
  3. 理解 Promise.allSettled/Promise.any 的适用场景,解决“批量任务全量结果统计”“多源数据容错获取”问题;
  4. 识别并解决 await 滥用导致的“并发变串行”问题,优化异步任务执行性能;
  5. 结合鸿蒙商品数据请求场景,落地 Async/await + Promise 进阶方案。

【学习重点】

  1. async/await 与 Promise 的底层关联(async 函数返回 Promise、await 等待 Promise 状态变更);
  2. try/catch 捕获 await 异步错误的核心逻辑,以及 finally 的收尾作用;
  3. Promise.allSettled/anyall/race 的场景对比,及代码实操;
  4. await 滥用的坑(并发变串行)及优化技巧(先创建 Promise 实例再批量 await)。

一、工程准备

本节基于上一节的 PromiseAsyncBasicDemo 工程扩展,复制一份重命PromiseAsyncBasicDemo_1,工程目录如下:

PromiseAsyncBasicDemo_1/
└── ets/
    ├── pages/
    │   └── Index.ets        // 新增2个按钮:Async/await、Promise进阶并发
    └── utils/               // 新增asyncawaitDemo、promiseAdvancedDemo静态方法
        └── PromiseUtil.ets  

二、为什么需要 Async/await?—— 链式调用的最后痛点

上一节用 Promise 链式调用解决了回调地狱,但仍有两个痛点:

  1. 代码冗余:多步异步操作需要嵌套 .then,逻辑越长代码越“碎”;
  2. 错误处理不够直观:虽然 catch 能统一捕获,但异步逻辑和错误处理分离,阅读成本高;
  3. 条件判断麻烦:链式调用中插入条件分支(比如“商品库存不足则终止请求”)会破坏链式结构。
  4. 核心解决方案async/await 是 Promise 的语法糖,让异步代码“看起来像同步”,逻辑更连贯。

三、Async/await 核心语法:异步代码“同步化”书写

3.1 基础规则

关键字 作用 核心说明
async 修饰函数 1. 加了async的函数,返回值自动包装为 Promise;
2. 函数内部才能使用await
await 等待异步结果 1. 只能在async函数内使用;
2. 等待 Promise 状态变为fulfilled/rejected
3. 成功则返回结果,失败则抛出错误(需try/catch捕获);

3.2 代码实操:Async/await 重构商品接口请求

对比上一节的 Promise 链式调用,用 async/await 重构“用户ID→用户详情→格式化”流程:

// ets/utils/PromiseUtil.ets(新增asyncawaitDemo方法)
export class PromiseUtil {
  // (延续上一节的所有方法,新增以下内容)
  /**
   * Async/await 核心演示:重构商品接口请求流程
   */
  static async asyncawaitDemo(): Promise<void> { // async函数返回Promise<void>
    console.log("===== Async/await 演示开始 =====");

    // 复用上一节的Promise封装函数(无需修改)
    const getUserIdPromise = (): Promise<string> => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          const isSuccess = Math.random() > 0.1;
          isSuccess ? resolve("user_1001") : reject(new Error("获取用户ID失败"));
        }, 1000);
      });
    };

    const getUserDetailPromise = (id: string): Promise<string> => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          const isSuccess = Math.random() > 0.1;
          isSuccess ? resolve(`ID:${id},姓名:鸿蒙开发者`) : reject(new Error("获取用户详情失败"));
        }, 1000);
      });
    };

    const formatDetailPromise = (detail: string): Promise<string> => {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          const isSuccess = Math.random() > 0.1;
          isSuccess ? resolve(`【用户信息】${detail}`) : reject(new Error("格式化详情失败"));
        }, 1000);
      });
    };

    // ===== 核心:Async/await + try/catch 处理 =====
    try {
      // 异步代码“同步化”书写,无需.then嵌套
      const id = await getUserIdPromise(); // 等待获取ID,成功返回结果
      const detail = await getUserDetailPromise(id); // 等待获取详情
      const result = await formatDetailPromise(detail); // 等待格式化

      console.log("Async/await 最终结果:", result); // 成功流程
   } catch (error) { // 捕获所有步骤的错误(任意一步失败都会触发)
      const errMsg = (error as Error ).message
      console.log("Async/await 错误:", errMsg);
    } finally {
      // 无论成功/失败,都会执行(比如隐藏加载动画)
      console.log("Async/await 流程结束(清理资源)");
      console.log("===== Async/await 演示结束 =====\n");
    }
  }
}

3.3 核心优势(对比 Promise 链式调用)

  1. 逻辑连贯:代码从上到下执行,和同步代码逻辑一致,无需嵌套 .then
  2. 错误处理直观try 包裹成功流程,catch 统一捕获所有异步错误,和同步错误处理方式一致;
  3. 条件判断友好:可在 await 之间插入任意条件分支(比如“ID为空则终止流程”),不破坏代码结构:
    try {
      const id = await getUserIdPromise();
      if (!id) {
        console.log("用户ID为空,终止流程");
        return; // 直接终止,无需链式调用的return Promise.reject()
      }
      const detail = await getUserDetailPromise(id);
      // ...后续逻辑
    } catch (error) { /* 错误处理 */ }
    

四、Try/Catch/Finally:异步错误处理的终极方案

4.1 同步 + 异步错误的统一捕获

try/catch 不仅能捕获 await 的异步错误,还能捕获同步代码错误,实现“一站式”错误处理:

// ets/utils/PromiseUtil.ets(补充错误处理演示)
static async errorHandleDemo(): Promise<void> {
  console.log("===== 同步+异步错误捕获演示开始 =====");
  try {
    // 同步错误:比如调用未定义的函数
    // (window as any).undefinedFunc(); // 取消注释可测试同步错误
    
    // 异步错误:Promise reject
    const id = await getUserIdPromise(); // 10%概率失败
    const detail = await getUserDetailPromise(id);
   
    // 同步错误:比如类型错误
    const num: number = detail.length; // 正常无错,模拟错误可改为 detail.length
    console.log("处理完成,数字:", num);
  } catch (error) {
    // 统一捕获:同步/异步错误都进入这里
    const errMsg = (error as Error).message;
    console.log("统一错误捕获:", errMsg);
  } finally {
    console.log("无论成败,都会执行的收尾逻辑");
    console.log("===== 错误捕获演示结束 =====\n");
  }
}

4.2 易错点提醒

⚠️ 易错点1:忘记给 awaittry/catch → 异步错误会变成“未捕获Promise错误”,导致程序崩溃;
⚠️ 易错点2await 后面跟非Promise值 → 会自动包装为 Promise.resolve(值),无报错但没必要;
⚠️ 易错点3async 函数返回非Promise值 → 会自动包装为Promise,比如 async fn() { return 1; } 等价于 return Promise.resolve(1)

五、Promise 进阶并发:AllSettled / Any

上一节学了 all(等所有成功)、race(等第一个完成),但真实业务中还有更多场景,比如:

  • “批量请求商品数据,无论成败都要统计结果(成功多少、失败多少)”;
  • “多源请求同一商品数据,只要有一个成功就用哪个(容错)”。

5.1 核心方法对比(进阶)

方法 适用场景 核心特点
Promise.allSettled 批量任务全量结果统计(比如批量商品接口请求) 1. 等待所有任务完成(无论成功/失败);
2. 返回结果数组,每个元素包含status(fulfilled/rejected)和value/reason
Promise.any 多源容错(比如多CDN请求同一商品图片) 1. 等待第一个成功的任务;
2. 所有任务都失败才会reject(返回AggregateError);

5.2 代码实操:AllSettled + Any 实战

// ets/utils/PromiseUtil.ets(新增promiseAdvancedDemo方法)
static async promiseAdvancedDemo(): Promise<void> {
  console.log("===== Promise进阶并发(allSettled/any)演示开始 =====");

  // 模拟3个商品接口请求(不同失败概率)
  const goodsApi1 = (): Promise<string> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => Math.random() > 0.3 ? resolve("商品1数据") : reject(new Error("商品1请求失败")), 1000);
    });
  };
  const goodsApi2 = (): Promise<string> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => Math.random() > 0.5 ? resolve("商品2数据") : reject(new Error("商品2请求失败")), 1500);
    });
  };
  const goodsApi3 = (): Promise<string> => {
    return new Promise((resolve, reject) => {
      setTimeout(() => Math.random() > 0.7 ? resolve("商品3数据") : reject(new Error("商品3请求失败")), 800);
    });
  };

  // 1. Promise.allSettled:批量商品请求结果统计
  const allSettledResult = await Promise.allSettled([goodsApi1(), goodsApi2(), goodsApi3()]);
  console.log("===== Promise.allSettled 结果 =====");
  // 统计成功/失败数量
  const successCount = allSettledResult.filter(item => item.status === "fulfilled").length;
  const failCount = allSettledResult.filter(item => item.status === "rejected").length;
  console.log(`成功:${successCount}个,失败:${failCount}个`);
  // 遍历结果
  allSettledResult.forEach((item, index) => {
    if (item.status === "fulfilled") {
      console.log(`商品${index+1}成功:`, item.value);
    } else {
      console.log(`商品${index+1}失败:`, (item.reason as Error).message);
    }
  });

  // 2. Promise.any:多源容错(取第一个成功的商品数据)
  try {
    const anyResult = await Promise.any([goodsApi1(), goodsApi2(), goodsApi3()]);
    console.log("\n===== Promise.any 结果 =====");
    console.log("第一个成功的商品数据:", anyResult);
  } catch (error) {
    // 所有任务都失败时触发(AggregateError)
    if (error instanceof AggregateError) {
      console.log("\nPromise.any 所有请求失败:", error.errors.map((e: Error) => e.message));
    }
  }

  console.log("===== Promise进阶并发演示结束 =====\n");
}

5.3 关键说明

  • allSettled 结果结构:
    // 成功项
    { status: "fulfilled", value: "商品1数据" }
    // 失败项
    { status: "rejected", reason: Error: 商品2请求失败 }
    
  • any 注意点:
    1. 优先返回第一个成功的结果,即使有任务先失败(比如商品3先失败,商品1后成功,仍返回商品1);
    2. 所有任务失败时,抛出 AggregateErrorerrors 属性包含所有失败原因。

六、避坑:await 滥用导致并发变串行

6.1 问题场景:批量商品请求变成串行

新手常犯的错误:用 for 循环 + await 批量请求商品,导致原本可并行的任务变成串行,耗时翻倍:

// 错误示例:串行请求(总耗时=1+1.5+0.8=3.3秒)
static async badawaitDemo(): Promise<void> {
  console.log("===== 错误:await滥用导致串行 =====");
  const start = Date.now();

  // 模拟3个商品请求(复用上面的goodsApi1/2/3)
  const result1 = await goodsApi1(); // 等1秒
  const result2 = await goodsApi2(); // 再等1.5秒
  const result3 = await goodsApi3(); // 再等0.8秒

  const totalTime = (Date.now() - start) / 1000;
  console.log(`串行请求总耗时:${totalTime.toFixed(1)}秒(本该1.5秒)`);
  console.log("===== 串行演示结束 =====\n");
}

6.2 优化方案:先创建Promise实例,再批量await(并行)

核心思路:先触发所有异步请求(创建Promise实例),再用 Promise.all 等待所有结果,实现并行:

// 正确示例:并行请求(总耗时=最长任务1.5秒)
static async goodawaitDemo(): Promise<void> {
  console.log("===== 正确:先创建Promise再await(并行) =====");
  const start = Date.now();

  // 第一步:先触发所有请求(创建Promise实例,异步任务开始执行)
  const promise1 = goodsApi1();
  const promise2 = goodsApi2();
  const promise3 = goodsApi3();

  // 第二步:批量等待所有结果(并行,总耗时=最长任务时间)
   const resultArr = await Promise.all([promise1, promise2, promise3]);

  const totalTime = (Date.now() - start) / 1000;
  console.log(`并行请求总耗时:${totalTime.toFixed(1)}秒(符合预期)`);
  console.log("===== 并行演示结束 =====\n");
}

6.3 核心原则

  • 串行await 写在循环/顺序执行中 → 任务依次执行,总耗时相加;
  • 并行:先创建所有Promise实例(触发异步任务),再用 Promise.all/allSettled 等待 → 任务同时执行,总耗时=最长任务时间。

七、入口页面扩展(Index.ets)

补充新按钮,触发本节的所有演示方法:

// ets/pages/Index.ets 完整代码
import { PromiseUtil } from '../utils/PromiseUtil';

@Entry
@Component
struct Index {
  build() {
    Column({ space: 15 }) {
      Text("Promise异步进阶(Async/await + 进阶并发)")
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(20);

      // 上一节按钮(保留)不展示。实际可查看代码工程
  
      // 本节新增按钮
      Button("6. Async/await 核心演示")
        .width('80%')
        .height(50)
        .onClick(() => PromiseUtil.asyncawaitDemo());
      Button("7. 同步+异步错误捕获")
        .width('80%')
        .height(50)
        .onClick(() => PromiseUtil.errorHandleDemo());
      Button("8. Promise.allSettled/any 进阶")
        .width('80%')
        .height(50)
        .onClick(() => PromiseUtil.promiseAdvancedDemo());
      Button("9. await避坑:串行vs并行")
        .width('80%')
        .height(50)
        .onClick(async () => {
          await PromiseUtil.badawaitDemo();
          await PromiseUtil.goodawaitDemo();
        });
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}

八、核心总结

  1. Async/await 核心async 函数返回 Promise,await 等待 Promise 结果,结合 try/catch 可直观处理异步错误,彻底替代链式调用;
  2. Promise 进阶并发
    • allSettled:批量任务全量结果统计(无论成败),适合商品批量请求的结果汇总;
    • any:多源容错,取第一个成功的结果,适合多CDN/多接口的容错场景;
  3. await 避坑
    • 串行:await 写在顺序执行中 → 总耗时相加;
    • 并行:先创建所有 Promise 实例,再用 Promise.all 等待 → 总耗时=最长任务时间;
  4. 错误处理try/catch 可统一捕获同步+异步错误,是鸿蒙异步开发的首选错误处理方式。

九、代码仓库

十、下节预告

本节我们吃透了 async/await 语法糖和 Promise 进阶并发方案,掌握了鸿蒙异步编程的核心能力。而实际开发中,异步获取的商品、订单等数据几乎都以 JSON 格式存在,JSON 的解析、存储与读写能力,是衔接异步数据和业务逻辑的关键桥梁。
下一节,我们将聚焦 JSON核心基础与鸿蒙文件读写实战,快速掌握:

  1. JSON 核心语法规则与强类型接口匹配技巧;
  2. ArkTS JSON 模块序列化/反序列化方法;
  3. rawfile 只读目录与沙箱可读写目录的 JSON 操作;
  4. JSON 处理避坑指南(文件句柄关闭、异步顺序、预览器限制)。
    通过本节学习,你将打通 “异步请求→JSON解析→本地存储” 的完整链路,为商品数据的本地缓存与业务处理做好准备。
posted @ 2026-01-25 12:10  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报