轻松搞定 HarmonyOS 异步并发:Promise 和 async/await 实战指南
本文深入讲解HarmonyOS应用开发中处理异步操作的两种核心方法——Promise和async/await。通过"烧水切菜"等生活化案例,形象说明异步编程的优势。重点解析Promise的三种状态(pending/fulfilled/rejected)及then/catch处理方法,以及async/await如何让异步代码更易读。
咱们在开发 HarmonyOS 应用的时候,经常会遇到这样的情况:比如发起网络请求后,得等服务器返回数据才能继续往下走;或者读取本地文件时,总不能让整个应用都卡着不动。这时候就需要用到异步并发能力了。今天咱们就好好聊聊 JavaScript 里处理异步的两大法宝 ——Promise 和 async/await,用最接地气的方式给大家讲明白,还附带实打实的 ArkTS 代码示例,保证看完就能用!
一、先搞懂:啥是异步并发?
说白了,异步并发就是程序执行的时候,不用傻等着一个操作完成,可以先去干别的活,等之前的操作有结果了再回来处理。这就好比咱们做饭,不用等水烧开了再去切菜,完全可以一边烧水一边准备食材,效率高多了!
重点总结:
- 异步操作不会阻塞后续代码执行,同一时间只执行一段代码
- 典型场景:网络请求、文件读写、定时器等 I/O 操作
- 适合处理轻量任务(单次执行时间短),且任务间有明确顺序或并行关系
- 核心优势:提升程序响应速度,避免界面卡顿
举个最简单的例子,咱们用定时器模拟一个异步操作:
// 同步代码:会阻塞后续执行
console.log("开始烧水");
// 模拟烧水需要3秒(同步阻塞)
let start = Date.now();
while (Date.now() - start < 3000) {}
console.log("水烧开了"); // 必须等3秒后才会执行
console.log("开始切菜"); // 被阻塞,只能在水开后执行
// 异步代码:不阻塞后续执行
console.log("开始烧水");
setTimeout(() => {
console.log("水烧开了"); // 3秒后执行
}, 3000);
console.log("开始切菜"); // 不用等,立即执行
看,上面的同步代码里,切菜必须等水烧开才能开始;而异步代码里,烧水的同时就可以切菜,这就是异步的好处!
二、Promise:异步操作的 "状态管家"
咱们先认识第一个工具 ——Promise。它就像给异步操作装了个 "状态追踪器",能清楚地知道操作是正在进行、成功完成还是失败了,让异步代码变得更规整。
1. Promise 的三种状态:从 pending 到最终结果
Promise 有三个状态,就像快递的物流状态一样:
- pending(进行中):刚创建 Promise 时的初始状态,就像快递刚发出
- fulfilled(已完成,也叫 resolved):异步操作成功完成,类似快递签收
- rejected(已拒绝):异步操作失败,比如快递丢失
重点总结:
- 状态一旦改变就不可逆:pending→fulfilled 或 pending→rejected 后,状态就固定了
- 状态改变由异步操作结果决定:成功调用 resolve () 变 fulfilled,失败调用 reject () 变 rejected
来看个状态变化的例子:
// 创建一个判断随机数大小的Promise
const promise: Promise<number> = new Promise((resolve: Function, reject: Function) => {
// 模拟1秒后完成的异步操作
setTimeout(() => {
const randomNum: number = Math.random(); // 生成0-1的随机数
if (randomNum > 0.5) {
resolve(randomNum); // 成功:状态变为fulfilled
} else {
reject(new Error("随机数太小啦!")); // 失败:状态变为rejected
}
}, 1000);
});
console.log("Promise刚创建时的状态:pending");
// 1秒后会根据随机数结果变成fulfilled或rejected
2. 用 then () 和 catch () 处理结果:给异步操作 "安排后事"
Promise 状态变了之后,咱们得知道结果啊!这时候就需要 then () 和 catch () 方法来处理成功或失败的情况。
重点总结:
- then () 可接收两个参数:第一个处理 fulfilled 状态,第二个处理 rejected 状态
- catch () 专门处理 rejected 状态,相当于 then (null, 失败回调)
- 回调函数会被加入微任务队列,等主线程空闲时执行
咱们给上面的 Promise 加上结果处理:
// 方法一:then()同时处理成功和失败
promise.then(
(result: number) => {
console.log(`成功!随机数是:${result}`); // 状态为fulfilled时执行
},
(error: Error) => {
console.error(`失败!原因:${error.message}`); // 状态为rejected时执行
}
);
// 方法二:then()处理成功,catch()处理失败(更推荐,代码更清晰)
promise
.then((result: number) => {
console.log(`成功!随机数是:${result}`);
})
.catch((error: Error) => {
console.error(`失败!原因:${error.message}`);
});
这里推荐用第二种方法,把成功和失败的处理分开写,代码看起来更清爽,也方便后续扩展。
3. 千万别漏了:未处理的 reject 会出大问题!
如果 Promise 被 reject 了,但咱们没写 catch () 处理,就会触发一个叫unhandledrejection的事件,这就好比快递丢了没人管,肯定会出问题。
重点总结:
- 未处理的 reject 会导致应用异常,甚至崩溃
- 可以全局监听
unhandledrejection事件,捕获漏网之鱼
// 全局监听未处理的Promise拒绝
import { errorManager } from '@kit.BasicServicesKit';
errorManager.on('unhandledrejection', (event: ErrorEvent) => {
console.error(`捕获到未处理的错误:${event.error.message}`);
event.preventDefault(); // 阻止默认处理(避免应用崩溃)
});
// 故意创建一个没有处理reject的Promise
new Promise((resolve, reject) => {
reject(new Error("这个错误没被处理哦!"));
});
加上全局监听后,就算哪里不小心忘了写 catch (),也能及时发现问题,不至于让应用莫名其妙崩溃。
三、async/await:让异步代码像同步一样好写
虽然 Promise 已经比回调函数好用多了,但如果遇到多个依赖的异步操作,then () 链式调用还是有点绕。这时候 async/await 就登场了,它是 Promise 的 "语法糖",能让异步代码写起来跟同步代码一样直观。
1. 基本用法:async 声明函数,await 等待结果
重点总结:
- 用
async关键字声明的函数,会自动返回一个 Promise - 函数内部用
await关键字等待 Promise 完成,会暂停执行直到 Promise 状态改变 - await 只能用在 async 函数里,不能直接用在普通函数中
咱们来看个例子,对比一下 Promise 和 async/await 的写法:
// 先用Promise写一个获取数据的函数
function fetchData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("这是从服务器拿到的数据");
}, 2000);
});
}
// 用Promise的then()处理
fetchData().then((data) => {
console.log(`拿到数据:${data}`);
return "处理后的数据";
}).then((processedData) => {
console.log(`最终结果:${processedData}`);
});
// 用async/await处理(是不是清爽多了?)
async function handleData() {
const data: string = await fetchData(); // 等待Promise完成,拿到结果
console.log(`拿到数据:${data}`);
const processedData: string = "处理后的数据";
console.log(`最终结果:${processedData}`);
return processedData; // async函数返回的是Promise
}
handleData();
看,用 async/await 之后,代码是从上到下顺序执行的,完全不用嵌套 then (),是不是跟写同步代码一样简单?
2. 错误处理:try/catch 捕获异常
await 后面的 Promise 如果被 reject 了,会抛出异常,这时候可以用 try/catch 来捕获,就像处理同步代码的错误一样。
重点总结:
- await 等待的 Promise 如果 reject,会触发异常
- 用 try/catch 块包裹 await 操作,捕获可能的错误
- 相当于 Promise 的 catch () 方法,但更符合同步代码的错误处理习惯
// 模拟一个可能失败的异步操作
function riskyOperation(): Promise<number> {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve(100); // 成功返回100
} else {
reject(new Error("操作失败啦!")); // 失败返回错误
}
}, 1500);
});
}
// 用try/catch处理错误
async function doRiskyThing() {
try {
console.log("开始执行危险操作...");
const result: number = await riskyOperation(); // 等待结果
console.log(`操作成功!结果是:${result}`);
return result;
} catch (error: any) { // 捕获reject抛出的错误
console.error(`操作失败:${error.message}`);
return -1; // 返回默认值
}
}
doRiskyThing();
这里用 try/catch 处理错误,比 Promise 的 catch () 更符合咱们平时写代码的习惯,一眼就能看出哪里可能出错,怎么处理。
3. 处理多个异步操作:并行和串行
实际开发中经常需要处理多个异步操作,有的要顺序执行(串行),有的可以同时执行(并行),用 async/await 处理起来很灵活。
重点总结:
- 串行:多个 await 依次写,前一个完成再执行后一个
- 并行:用 Promise.all () 包装多个 Promise,再用 await 等待,效率更高
// 模拟两个异步操作
function asyncTask1(): Promise<number> {
return new Promise(resolve => setTimeout(() => resolve(1), 1000));
}
function asyncTask2(): Promise<number> {
return new Promise(resolve => setTimeout(() => resolve(2), 2000));
}
// 1. 串行执行:总耗时≈1+2=3秒
async function serialTasks() {
console.log("开始串行执行...");
const start = Date.now();
const result1 = await asyncTask1(); // 等1秒
const result2 = await asyncTask2(); // 再等2秒
const totalTime = (Date.now() - start) / 1000;
console.log(`串行结果:${result1}, ${result2},耗时${totalTime}秒`);
}
// 2. 并行执行:总耗时≈2秒(取最长的那个)
async function parallelTasks() {
console.log("开始并行执行...");
const start = Date.now();
// 同时启动两个任务
const promise1 = asyncTask1();
const promise2 = asyncTask2();
// 等待两个都完成
const [result1, result2] = await Promise.all([promise1, promise2]);
const totalTime = (Date.now() - start) / 1000;
console.log(`并行结果:${result1}, ${result2},耗时${totalTime}秒`);
}
serialTasks();
setTimeout(parallelTasks, 4000); // 等串行执行完再跑并行
从结果能明显看出,并行执行比串行快很多。所以如果多个异步操作之间没有依赖关系,一定要用并行的方式,能大大提高效率。
4. 注意事项:这些坑千万别踩!
虽然 async/await 很好用,但有几个注意点得记牢,不然容易出问题:
重点总结:
- await 会暂停当前 async 函数,但不会阻塞其他代码
- 不要在循环里用 await(会变成串行,效率低)
- 多个无依赖的 await 要改用 Promise.all ()
// 反面例子:循环里用await,导致串行执行
async function badLoop() {
const tasks = [1, 2, 3];
const results: number[] = [];
for (const task of tasks) {
// 每次都等上一个完成,总耗时≈3秒
const result = await new Promise(resolve =>
setTimeout(() => resolve(task * 10), 1000)
);
results.push(result);
}
console.log("循环结果(串行):", results);
}
// 正面例子:用Promise.all()并行处理循环任务
async function goodLoop() {
const tasks = [1, 2, 3];
// 先创建所有Promise(同时启动)
const promises = tasks.map(task =>
new Promise(resolve =>
setTimeout(() => resolve(task * 10), 1000)
)
);
// 等待所有完成(总耗时≈1秒)
const results = await Promise.all(promises);
console.log("循环结果(并行):", results);
}
badLoop();
setTimeout(goodLoop, 4000);
同样是处理 3 个任务,坏例子用了 3 秒,好例子只用了 1 秒,效率差了 3 倍!所以写循环的时候一定要注意这个问题。
四、实战场景:在 HarmonyOS 组件中用异步
咱们开发 HarmonyOS 应用时,经常需要在 UI 组件里处理异步操作(比如点击按钮后请求数据)。下面就用一个实际场景,看看怎么把 Promise 和 async/await 用到组件里。
场景:点击按钮后,先请求用户信息,再用用户 ID 请求用户订单,最后显示结果。
@Entry
@Component
struct AsyncDemoPage {
@State message: string = "点击按钮加载数据";
@State loading: boolean = false;
// 模拟请求用户信息(1秒返回)
private fetchUserInfo(): Promise<{ id: number; name: string }> {
return new Promise(resolve => {
setTimeout(() => {
resolve({ id: 1001, name: "小明" });
}, 1000);
});
}
// 模拟用用户ID请求订单(2秒返回)
private fetchOrders(userId: number): Promise<{ id: number; total: number }[]> {
return new Promise(resolve => {
setTimeout(() => {
resolve([
{ id: 1, total: 99 },
{ id: 2, total: 199 }
]);
}, 2000);
});
}
// 处理点击事件的异步函数
private async handleClick() {
this.loading = true;
this.message = "加载中...";
try {
// 先获取用户信息
const user = await this.fetchUserInfo();
// 再用用户ID获取订单(依赖上一步结果,必须串行)
const orders = await this.fetchOrders(user.id);
// 显示结果
this.message = `${user.name}的订单:共${orders.length}个,总金额${orders.reduce((sum, o) => sum + o.total, 0)}元`;
} catch (error: any) {
this.message = `加载失败:${error.message}`;
} finally {
this.loading = false;
}
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(16)
.margin(20)
Button(this.loading ? "加载中..." : "点击加载数据")
.fontSize(16)
.padding({ left: 20, right: 20 })
.onClick(() => this.handleClick())
.enabled(!this.loading) // 加载中禁用按钮
}
.width('100%')
.height('100%')
}
}
}
这个例子里,咱们用 async/await 把两个有依赖的异步操作(先用户信息,后订单)写得像同步代码一样直观,还加了 loading 状态防止重复点击,用 try/catch 处理可能的错误,非常实用。
五、总结:Promise 和 async/await 怎么选?
看到这里,可能有同学会问:既然 async/await 这么好用,那是不是可以不用 Promise 了?其实不是的,它们俩是相辅相成的关系:
- Promise 是基础:async/await 本质上还是基于 Promise 的,所有 async 函数都返回 Promise
- 简单场景用 Promise:单个异步操作,用 then () 更简洁
- 复杂场景用 async/await:多个依赖的异步操作,或者需要和同步代码混写时,async/await 更清晰
核心记住这几点:
- 异步操作用 Promise 包装,明确状态和结果
- 单个异步用 then ()/catch (),多个依赖用 async/await
- 并行操作优先用 Promise.all (),提高效率
- 别忘全局监听 unhandledrejection,兜底防崩溃
掌握了这两个工具,不管是处理网络请求、文件读写还是定时器,都能游刃有余,写出既高效又好维护的代码。赶紧在自己的项目里试试吧,实践几次就能熟练掌握啦!
更多推荐



所有评论(0)