零基础鸿蒙应用开发第三十一节:await简化异步编程与Promise进阶并发
【学习目标】
- 掌握
async/await语法糖的核心用法,将 Promise 异步代码“同步化”书写,彻底摆脱链式调用冗余; - 熟练使用
try/catch/finally处理异步错误,覆盖同步+异步场景的统一错误捕获; - 理解
Promise.allSettled/Promise.any的适用场景,解决“批量任务全量结果统计”“多源数据容错获取”问题; - 识别并解决
await滥用导致的“并发变串行”问题,优化异步任务执行性能; - 结合鸿蒙商品数据请求场景,落地 Async/await + Promise 进阶方案。
【学习重点】
async/await与 Promise 的底层关联(async函数返回 Promise、await等待 Promise 状态变更);try/catch捕获await异步错误的核心逻辑,以及finally的收尾作用;Promise.allSettled/any与all/race的场景对比,及代码实操;await滥用的坑(并发变串行)及优化技巧(先创建 Promise 实例再批量 await)。
一、工程准备
本节基于上一节的 PromiseAsyncBasicDemo 工程扩展,复制一份重命PromiseAsyncBasicDemo_1,工程目录如下:
PromiseAsyncBasicDemo_1/
└── ets/
├── pages/
│ └── Index.ets // 新增2个按钮:Async/await、Promise进阶并发
└── utils/ // 新增asyncawaitDemo、promiseAdvancedDemo静态方法
└── PromiseUtil.ets
二、为什么需要 Async/await?—— 链式调用的最后痛点
上一节用 Promise 链式调用解决了回调地狱,但仍有两个痛点:
- 代码冗余:多步异步操作需要嵌套
.then,逻辑越长代码越“碎”; - 错误处理不够直观:虽然
catch能统一捕获,但异步逻辑和错误处理分离,阅读成本高; - 条件判断麻烦:链式调用中插入条件分支(比如“商品库存不足则终止请求”)会破坏链式结构。
- 核心解决方案:
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 链式调用)
- 逻辑连贯:代码从上到下执行,和同步代码逻辑一致,无需嵌套
.then; - 错误处理直观:
try包裹成功流程,catch统一捕获所有异步错误,和同步错误处理方式一致; - 条件判断友好:可在
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:忘记给 await 加 try/catch → 异步错误会变成“未捕获Promise错误”,导致程序崩溃;
⚠️ 易错点2:await 后面跟非Promise值 → 会自动包装为 Promise.resolve(值),无报错但没必要;
⚠️ 易错点3:async 函数返回非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注意点:- 优先返回第一个成功的结果,即使有任务先失败(比如商品3先失败,商品1后成功,仍返回商品1);
- 所有任务失败时,抛出
AggregateError,errors属性包含所有失败原因。
六、避坑: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);
}
}
八、核心总结
- Async/await 核心:
async函数返回 Promise,await等待 Promise 结果,结合try/catch可直观处理异步错误,彻底替代链式调用; - Promise 进阶并发:
allSettled:批量任务全量结果统计(无论成败),适合商品批量请求的结果汇总;any:多源容错,取第一个成功的结果,适合多CDN/多接口的容错场景;
- await 避坑:
- 串行:
await写在顺序执行中 → 总耗时相加; - 并行:先创建所有 Promise 实例,再用
Promise.all等待 → 总耗时=最长任务时间;
- 串行:
- 错误处理:
try/catch可统一捕获同步+异步错误,是鸿蒙异步开发的首选错误处理方式。
九、代码仓库
- 工程名称:
PromiseAsyncBasicDemo_1 - 仓库地址:https://gitee.com/juhetianxia321/harmony-os-code-base.git
十、下节预告
本节我们吃透了 async/await 语法糖和 Promise 进阶并发方案,掌握了鸿蒙异步编程的核心能力。而实际开发中,异步获取的商品、订单等数据几乎都以 JSON 格式存在,JSON 的解析、存储与读写能力,是衔接异步数据和业务逻辑的关键桥梁。
下一节,我们将聚焦 JSON核心基础与鸿蒙文件读写实战,快速掌握:
- JSON 核心语法规则与强类型接口匹配技巧;
- ArkTS
JSON模块序列化/反序列化方法; rawfile只读目录与沙箱可读写目录的 JSON 操作;- JSON 处理避坑指南(文件句柄关闭、异步顺序、预览器限制)。
通过本节学习,你将打通 “异步请求→JSON解析→本地存储” 的完整链路,为商品数据的本地缓存与业务处理做好准备。
浙公网安备 33010602011771号