Day 17:Promise 基础
学习目标:理解异步编程的必要性,掌握 Promise 的基本用法,能够用 Promise 替代回调地狱,理解单线程异步与 C++ 多线程的根本区别。
第一部分:为什么需要异步编程
1.1 同步执行的问题 —— 耗时操作阻塞后续代码
在 C/C++ 中,你一定遇到过这种情况:
// C 语言示例:同步读取文件
FILE *fp = fopen("data.txt", "r");
char buffer[1024];
// 阻塞!直到文件读取完成
fread(buffer, 1, 1024, fp);
// 这行代码必须等上面读完才能执行
printf("读取完成\n");
问题:在 ArkTS 中,如果像上面这样执行耗时操作,会发生什么?
// 假设这是同步的网络请求(伪代码)
let data: string = syncHttpRequest("https://api.example.com/data"); // 阻塞3秒
console.log("请求完成,数据:" + data);
console.log("这行等了3秒才执行");
答案:整个程序会卡住,用户界面无响应。这在 ArkTS 中是致命的,因为 ArkTS 是单线程的。
1.2 C++ 的解决方案 —— 多线程
在 C++ 中,面对耗时操作,标准做法是开新线程:
// C++ 多线程方案
#include <thread>
#include <future>
void fetchData() {
// 在后台线程执行耗时操作
std::string data = httpRequest("https://api.example.com/data");
return data;
}
int main() {
// 启动异步任务
std::future<std::string> future = std::async(std::launch::async, fetchData);
// 主线程继续执行,不被阻塞
std::cout << "请求已发送,继续执行其他代码" << std::endl;
// 需要结果时再获取(可能阻塞,也可能已准备好)
std::string result = future.get();
std::cout << "结果:" << result << std::endl;
return 0;
}
C++ 的核心思路:用多线程实现并发,耗时操作丢给子线程,主线程继续跑。
1.3 ArkTS 的解决方案 —— 单线程 + 异步非阻塞
关键认知转变:ArkTS(以及 JavaScript/TypeScript)没有多线程(Worker 是特殊情况,先不考虑)。
那怎么解决阻塞问题?答案是:异步非阻塞 + 事件循环。
// ArkTS 异步方案
function fetchData(): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
// 模拟异步操作(如网络请求)
setTimeout((): void => {
resolve("数据获取成功");
}, 3000);
});
}
// 调用异步函数,不会阻塞
fetchData().then((data: string): void => {
console.log("收到数据:" + data);
});
// 这行代码**立即执行**,不会等上面的3秒
console.log("请求已发送,我继续执行");
输出顺序:
请求已发送,我继续执行
(3秒后)
收到数据:数据获取成功
核心区别:
| 特性 | C++ 多线程 | ArkTS 单线程异步 |
|---|---|---|
| 执行实体 | 多个 OS 线程 | 单个事件循环 |
| 并发方式 | 真正并行(多核) | 协作式调度(任务队列) |
| 状态共享 | 需要锁/原子操作 | 无竞态条件(单线程) |
| 复杂度 | 线程同步复杂 | 回调/Promise 链 |
| 适用场景 | CPU 密集型计算 | I/O 密集型操作 |
1.4 生活类比 —— 餐厅点菜
同步阻塞模式:
- 你走进餐厅,点了一份牛排
- 你站在柜台前,盯着厨师做,什么都不干
- 30分钟后牛排好了,你拿着吃
- 问题:这30分钟你浪费了
异步非阻塞模式:
- 你走进餐厅,点了一份牛排,服务员给你一个"叫号器"
- 你去找座位、刷手机、聊天(做其他事)
- 牛排好了,叫号器震动,你去取餐
- 优势:等待期间你能做其他事
Promise 就是那个"叫号器" —— 它代表一个"未来会完成的操作",完成后通知你。
第二部分:回调的局限(铺垫)
2.1 回调函数回顾
在 Day 07 函数高级中,我们学过回调函数。它是最原始的异步处理方式:
// 模拟异步读取文件(用 setTimeout 模拟延迟)
function readFileAsync(
path: string,
callback: (error: Error | null, data: string | null) => void
): void {
setTimeout(() => {
if (path === "") {
callback(new Error("路径不能为空"), null);
} else {
callback(null, "文件内容:Hello ArkTS");
}
}, 1000);
}
// 使用回调
readFileAsync("test.txt", (err: Error | null, data: string | null) => {
if (err !== null) {
console.log("错误:" + err.message);
} else {
console.log("成功:" + data);
}
});
2.2 回调地狱
当多个异步操作需要顺序执行时,代码会变成这样:
// 回调地狱示例:先读配置,再连数据库,再查数据
readConfig("config.json", (err: Error | null, config: string | null) => {
if (err !== null) {
console.log("读配置失败");
return;
}
connectDatabase(config, (err: Error | null, db: string | null) => {
if (err !== null) {
console.log("连数据库失败");
return;
}
queryData(db, "SELECT * FROM users", (err: Error | null, result: string | null) => {
if (err !== null) {
console.log("查询失败");
return;
}
processResult(result, (err: Error | null, output: string | null) => {
if (err !== null) {
console.log("处理失败");
return;
}
console.log("最终结果:" + output);
});
});
});
});
问题:
- 代码横向发展,形成"金字塔"结构
- 每一层都要处理错误,重复代码多
- 逻辑嵌套深,可读性差
- 错误处理分散,容易遗漏
2.3 错误处理困难
在回调模式中,每个回调都要单独处理错误:
function doStep1(callback: (err: Error | null, result: string | null) => void): void {
setTimeout(() => {
// 50% 概率失败
if (Math.random() > 0.5) {
callback(new Error("步骤1失败"), null);
} else {
callback(null, "步骤1结果");
}
}, 100);
}
// 每一步都要写 if (err) 处理错误
doStep1((err: Error | null, result: string | null) => {
if (err !== null) {
// 错误处理 1
console.log("错误:" + err.message);
return;
}
doStep2(result, (err: Error | null, result2: string | null) => {
if (err !== null) {
// 错误处理 2
console.log("错误:" + err.message);
return;
}
// ... 更多嵌套
});
});
引出:我们需要一种更好的异步模式,能够:
- 扁平化代码结构(不要嵌套)
- 集中处理错误(不要每个回调都写错误判断)
- 更优雅地组合多个异步操作
这就是 Promise 要解决的问题。
第三部分:Promise 基础
3.1 Promise 是什么
Promise 是对"未来某个时刻会得到的值"的承诺。
对比 C++ 的 std::future:
// C++ std::future
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
// 在另一个线程设置值
promise.set_value("数据准备好了");
// 在主线程获取值
std::string data = future.get(); // 阻塞等待
// ArkTS Promise
let promise: Promise<string> = new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => {
resolve("数据准备好了"); // 类似 promise.set_value()
}, 1000);
});
// 通过 then() 获取结果(非阻塞)
promise.then((data: string): void => {
console.log("收到:" + data);
});
核心概念:
- Promise 是一个对象,代表一个尚未完成但预期将来会完成的操作
- 它有三个状态:Pending(进行中)、Fulfilled(成功)、Rejected(失败)
- 状态一旦改变(Pending → Fulfilled 或 Pending → Rejected),就不可再次改变
3.2 三态模型
┌─────────┐ resolve() ┌───────────┐
│ Pending │ ──────────────→ │ Fulfilled │
│ (等待) │ │ (成功) │
└─────────┘ └───────────┘
│
│ reject()
↓
┌───────────┐
│ Rejected │
│ (失败) │
└───────────┘
状态规则:
- Pending:初始状态,操作正在进行中
- Fulfilled:操作成功完成,有结果值
- Rejected:操作失败,有错误原因
- 不可变性:状态一旦改变,不能再变(Fulfilled 不能变 Rejected,反之亦然)
// 状态变化示例
let promise: Promise<string> = new Promise<string>(
(resolve: (value: string) => void, reject: (reason: Error) => void): void => {
// 当前状态:Pending
setTimeout((): void => {
if (Math.random() > 0.5) {
resolve("成功"); // 状态变为 Fulfilled
} else {
reject(new Error("失败")); // 状态变为 Rejected
}
}, 1000);
}
);
3.3 创建 Promise
语法:
new Promise<T>(
(resolve: (value: T) => void, reject: (reason: Error) => void): void => {
// 异步操作代码
}
);
参数说明:
T:泛型参数,指定 Promise 成功时返回的数据类型(必须显式声明)- 构造函数接收一个执行器函数,该函数有两个参数:
resolve:成功时调用,将 Promise 状态变为 Fulfilledreject:失败时调用,将 Promise 状态变为 Rejected
// 示例:创建一个返回数字的 Promise
let numberPromise: Promise<number> = new Promise<number>(
(resolve: (value: number) => void, reject: (reason: Error) => void): void => {
setTimeout((): void => {
resolve(42);
}, 1000);
}
);
// 示例:创建一个返回字符串数组的 Promise
let arrayPromise: Promise<string[]> = new Promise<string[]>(
(resolve: (value: string[]) => void, reject: (reason: Error) => void): void => {
setTimeout((): void => {
resolve(["a", "b", "c"]);
}, 500);
}
);
// 示例:创建一个可能失败的 Promise
let riskyPromise: Promise<string> = new Promise<string>(
(resolve: (value: string) => void, reject: (reason: Error) => void): void => {
setTimeout((): void => {
if (Math.random() > 0.5) {
resolve("运气不错");
} else {
reject(new Error("运气不好"));
}
}, 100);
}
);
3.4 使用 resolve 和 reject
resolve 用法:
function fetchUserData(userId: number): Promise<string> {
return new Promise<string>(
(resolve: (value: string) => void, reject: (reason: Error) => void): void => {
setTimeout((): void => {
if (userId <= 0) {
reject(new Error("无效的用户ID"));
} else {
// 成功时调用 resolve,传递结果值
resolve("用户" + userId.toString() + "的数据");
}
}, 1000);
}
);
}
// 调用
fetchUserData(100).then((data: string): void => {
console.log("获取成功:" + data);
});
reject 用法:
function divide(a: number, b: number): Promise<number> {
return new Promise<number>(
(resolve: (value: number) => void, reject: (reason: Error) => void): void => {
if (b === 0) {
// 失败时调用 reject,传递 Error 对象
reject(new Error("除数不能为0"));
} else {
resolve(a / b);
}
}
);
}
// 调用
divide(10, 0).then(
(result: number): void => {
console.log("结果:" + result.toString());
},
(error: Error): void => {
console.log("错误:" + error.message);
}
);
重要规则:
- resolve 和 reject 只能调用一次,多次调用无效
- reject 通常传递 Error 对象(便于错误追踪)
- 执行器函数中的同步错误会自动转为 reject
// 同步错误自动转为 reject
let errorPromise: Promise<string> = new Promise<string>(
(resolve: (value: string) => void, reject: (reason: Error) => void): void => {
// 这行会抛出异常
throw new Error("出错了");
// 等价于:reject(new Error("出错了"));
}
);
// 可以用 catch 捕获
errorPromise.catch((error: Error): void => {
console.log("捕获错误:" + error.message);
});
第四部分:Promise 链式调用
4.1 then() 处理成功
基本用法:
let promise: Promise<string> = new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => {
resolve("Hello");
}, 1000);
});
// then() 接收一个回调,在 Promise 成功时执行
promise.then((value: string): void => {
console.log("收到值:" + value);
});
then() 的返回值:
then() 方法返回一个新的 Promise,这使得链式调用成为可能:
let promise: Promise<number> = new Promise<number>((resolve: (value: number) => void): void => {
resolve(10);
});
// then() 返回新的 Promise
let newPromise: Promise<string> = promise.then((value: number): string => {
return "数字是:" + value.toString();
});
// 可以继续链式调用
newPromise.then((str: string): void => {
console.log(str); // 输出:数字是:10
});
4.2 catch() 处理失败
基本用法:
let promise: Promise<string> = new Promise<string>(
(resolve: (value: string) => void, reject: (reason: Error) => void): void => {
reject(new Error("操作失败"));
}
);
// catch() 捕获 Promise 链中的任何错误
promise.catch((error: Error): void => {
console.log("捕获到错误:" + error.message);
});
对比 C++ 异常处理:
// C++ try/catch
try {
throw std::runtime_error("出错了");
} catch (const std::exception& e) {
std::cout << "捕获:" << e.what() << std::endl;
}
// ArkTS Promise catch
new Promise<string>(
(resolve: (value: string) => void, reject: (reason: Error) => void): void => {
reject(new Error("出错了"));
}
).catch((error: Error): void => {
console.log("捕获:" + error.message);
});
4.3 finally() 最终处理
作用:无论 Promise 成功还是失败,finally 都会执行。类似 C++ 的 try/catch/finally。
function doSomething(): Promise<string> {
return new Promise<string>(
(resolve: (value: string) => void, reject: (reason: Error) => void): void => {
setTimeout((): void => {
if (Math.random() > 0.5) {
resolve("成功");
} else {
reject(new Error("失败"));
}
}, 1000);
}
);
}
doSomething()
.then((result: string): void => {
console.log("结果:" + result);
})
.catch((error: Error): void => {
console.log("错误:" + error.message);
})
.finally((): void => {
// 无论成功失败,这里都会执行
console.log("操作完成(清理资源等)");
});
适用场景:
- 关闭文件句柄
- 释放资源
- 隐藏加载动画
- 重置状态
4.4 链式调用解决回调地狱
对比:回调地狱 vs Promise 链
// ========== 回调地狱写法 ==========
readConfig("config.json", (err: Error | null, config: string | null) => {
if (err !== null) {
console.log("错误:" + err.message);
return;
}
connectDB(config, (err: Error | null, db: string | null) => {
if (err !== null) {
console.log("错误:" + err.message);
return;
}
queryData(db, (err: Error | null, data: string | null) => {
if (err !== null) {
console.log("错误:" + err.message);
return;
}
console.log("数据:" + data);
});
});
});
// ========== Promise 链式写法 ==========
readConfigPromise("config.json")
.then((config: string) => {
return connectDBPromise(config);
})
.then((db: string) => {
return queryDataPromise(db);
})
.then((data: string) => {
console.log("数据:" + data);
})
.catch((error: Error) => {
// 统一处理所有错误!
console.log("错误:" + error.message);
});
链式调用的优势:
- 代码扁平化,不再是金字塔
- 错误处理集中,一个 catch 捕获所有错误
- 每一步的逻辑更清晰
链式调用简写:
// 当箭头函数直接返回 Promise 时,可以简写
readConfigPromise("config.json")
.then((config: string) => connectDBPromise(config)) // 省略 return 和 {}
.then((db: string) => queryDataPromise(db))
.then((data: string) => {
console.log("数据:" + data);
return processData(data);
})
.catch((error: Error) => {
console.log("错误:" + error.message);
});
重要:单参数箭头函数必须保留括号,这是 ArkTS 语法要求。
第五部分:Promise 组合方法
5.1 Promise.all —— 全部成功才成功
作用:等待多个 Promise 全部完成,返回一个包含所有结果的数组。
对标 C++:类似等待所有线程完成(std::thread::join 所有线程)。
// 模拟三个独立的异步操作
function task1(): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("任务1结果"), 1000);
});
}
function task2(): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("任务2结果"), 2000);
});
}
function task3(): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("任务3结果"), 1500);
});
}
// 等待所有任务完成
Promise.all([task1(), task2(), task3()])
.then((results: string[]): void => {
console.log("所有任务完成");
console.log("结果1:" + results[0]);
console.log("结果2:" + results[1]);
console.log("结果3:" + results[2]);
})
.catch((error: Error): void => {
// 任意一个任务失败,就会进入这里
console.log("有任务失败:" + error.message);
});
行为规则:
- 所有 Promise 都成功 → then() 接收结果数组
- 任意一个 Promise 失败 → catch() 捕获第一个错误
- 总耗时 = 最慢的那个 Promise 的耗时(并行执行)
// 失败示例
let p1: Promise<string> = Promise.resolve<string>("成功1");
let p2: Promise<string> = Promise.reject<string>(new Error("失败"));
let p3: Promise<string> = Promise.resolve<string>("成功2");
Promise.all([p1, p2, p3])
.then((results: string[]): void => {
console.log("不会执行到这里");
})
.catch((error: Error): void => {
console.log("捕获:" + error.message); // 输出:捕获:失败
});
5.2 Promise.race —— 取最先完成的结果
作用:多个 Promise 竞争,返回最先完成的那个(无论成功或失败)。
对标 C++:类似竞争条件,谁先完成用谁的结果。
// 模拟网络请求超时控制
function fetchWithTimeout(url: string, timeout: number): Promise<string> {
let fetchPromise: Promise<string> = new Promise<string>(
(resolve: (value: string) => void): void => {
setTimeout((): void => {
resolve("从" + url + "获取的数据");
}, 2000); // 模拟2秒的网络请求
}
);
let timeoutPromise: Promise<never> = new Promise<never>(
(resolve: (value: never) => void, reject: (reason: Error) => void): void => {
setTimeout((): void => {
reject(new Error("请求超时"));
}, timeout);
}
);
// 谁先完成用谁的结果
return Promise.race<string>([fetchPromise, timeoutPromise]);
}
// 设置1秒超时,但请求需要2秒
fetchWithTimeout("https://api.example.com", 1000)
.then((data: string): void => {
console.log("获取成功:" + data);
})
.catch((error: Error): void => {
console.log("失败:" + error.message); // 输出:失败:请求超时
});
重要副作用说明:
Promise.race 返回最快完成的 Promise 结果,但其他 Promise 不会自动终止!
let fast: Promise<string> = new Promise<string>(
(resolve: (value: string) => void): void => {
setTimeout((): void => resolve("快"), 100);
}
);
let slow: Promise<string> = new Promise<string>(
(resolve: (value: string) => void): void => {
setTimeout((): void => {
console.log("slow 执行了"); // 这行仍然会打印!
resolve("慢");
}, 1000);
}
);
Promise.race([fast, slow]).then((result: string): void => {
console.log("race 结果:" + result); // "快"
});
// 输出顺序:
// race 结果:快
// slow 执行了 ← slow 的 setTimeout 仍会执行!
原因:Promise 一旦创建就会执行到底,没有内置的取消机制。race 只取结果,不终止执行。
适用场景:
- 请求超时控制
- 多个数据源竞争(用最快的)
- 缓存优先 vs 网络优先
5.3 Promise.allSettled —— 等全部完成,不论成败
作用:等待所有 Promise 完成(无论成功或失败),返回每个 Promise 的状态和结果。
对比 Promise.all:
| 方法 | 成功条件 | 失败处理 |
|---|---|---|
| Promise.all | 全部成功 | 一个失败就失败 |
| Promise.allSettled | 全部完成(无论成败) | 不会失败,返回所有状态 |
标准 TypeScript 中的用法
在标准 TypeScript 中,Promise.allSettled 返回一个数组,每个元素包含 status("fulfilled" 或 "rejected")和对应的值或错误原因:
// 标准 TypeScript(非 ArkTS)示例
let promises = [
Promise.resolve("成功1"),
Promise.reject(new Error("失败1")),
Promise.resolve("成功2")
];
Promise.allSettled(promises)
.then((results) => {
results.forEach((result) => {
if (result.status === "fulfilled") {
console.log("成功:" + result.value);
} else {
console.log("失败:" + result.reason.message);
}
});
});
ArkTS 中的限制
重要:Promise.allSettled 在 ArkTS 中使用受限!
原因如下:
- 返回联合类型:
allSettled返回PromiseSettledResult<T>[],其中PromiseSettledResult是联合类型(fulfilled | rejected) - ArkTS 不支持解构赋值:无法方便地提取
status和value - ArkTS 不支持索引访问:无法使用
result["status"]访问属性
因此,在 ArkTS 中如果需要等待多个 Promise 且处理部分失败,建议:
- 使用
Promise.all+ 统一错误处理 - 或者逐个处理每个 Promise 的结果
适用场景(概念层面):
- 批量操作,需要知道每个操作的结果
- 容错场景,部分失败不影响整体
- 日志记录、批量上报
第六部分:小结与练习
知识点对比总结表
| 概念 | ArkTS Promise | C++ 类比 |
|---|---|---|
| 异步表示 | Promise<T> |
std::future<T> |
| 设置结果 | resolve(value) |
promise.set_value(value) |
| 获取结果 | then(callback) |
future.get()(但非阻塞) |
| 错误处理 | reject(error) + catch() |
异常机制 |
| 等待多个 | Promise.all() |
多线程 join |
| 竞争 | Promise.race() |
竞争条件 |
| 执行模型 | 单线程 + 事件循环 | 多线程并发 |
单线程异步 vs 多线程并发的根本区别:
| 特性 | ArkTS 单线程异步 | C++ 多线程并发 |
|---|---|---|
| 并行性 | 无真正并行(I/O 交给 OS) | 真正并行(多核) |
| 竞态条件 | 不存在(单线程) | 需要锁保护 |
| 死锁 | 不可能 | 可能 |
| 适用场景 | I/O 密集型 | CPU 密集型 |
| 编程复杂度 | 回调/Promise 链 | 线程同步 |
练习题
练习 1:基础 Promise 创建
创建一个 delay 函数,接收毫秒数,返回一个 Promise,在指定时间后 resolve。
function delay(ms: number): Promise<void> {
// 你的代码
}
// 使用
delay(1000).then(() => {
console.log("1秒后执行");
});
点击查看答案
function delay(ms: number): Promise<void> {
return new Promise<void>((resolve: () => void): void => {
setTimeout(resolve, ms);
});
}
// 使用
delay(1000).then((): void => {
console.log("1秒后执行");
});
要点:
new Promise<void>显式声明泛型为voidsetTimeout可以直接传resolve,不需要包装
练习 2:Promise 链式调用
实现一个计算流程:先获取数字 5,然后乘以 2,再加 10,最后打印结果。要求使用 Promise 链。
点击查看答案
function getNumber(): Promise<number> {
return Promise.resolve<number>(5);
}
getNumber()
.then((value: number): number => {
return value * 2;
})
.then((value: number): number => {
return value + 10;
})
.then((value: number): void => {
console.log("结果:" + value.toString()); // 20
});
要点:
- 每个
then接收前一个then的返回值 - 最后一个
then返回void,用于副作用(打印)
练习 3:错误处理
编写一个 safeDivide 函数,接收两个数字,如果除数为 0 则 reject,否则 resolve 商。使用 catch 处理错误。
点击查看答案
function safeDivide(a: number, b: number): Promise<number> {
return new Promise<number>(
(resolve: (value: number) => void, reject: (reason: Error) => void): void => {
if (b === 0) {
reject(new Error("除数不能为0"));
} else {
resolve(a / b);
}
}
);
}
// 使用
safeDivide(10, 0)
.then((result: number): void => {
console.log("结果:" + result.toString());
})
.catch((error: Error): void => {
console.log("错误:" + error.message);
});
要点:
- 除数为 0 时调用
reject,传递Error对象 - 使用
catch统一捕获错误,比在每个then中处理更简洁
练习 4:Promise.all 实践
模拟同时加载三张图片(用 setTimeout 模拟),每张加载时间不同。使用 Promise.all 等待全部加载完成,然后打印"所有图片加载完成"。
点击查看答案
function loadImage(url: string, delay: number): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => {
console.log("加载完成:" + url);
resolve(url);
}, delay);
});
}
Promise.all<string>([
loadImage("图片1", 1000),
loadImage("图片2", 2000),
loadImage("图片3", 1500)
])
.then((results: string[]): void => {
console.log("所有图片加载完成");
console.log("结果:" + results.join(", "));
});
要点:
Promise.all<string>显式声明泛型- 总耗时 = 最慢的那个(2秒)
- 结果数组顺序与传入顺序一致
练习 5:Promise.race 实践
实现一个函数 fetchWithRetry,尝试从两个不同的 URL 获取数据,使用 Promise.race 取最快返回的结果。
点击查看答案
function fetchFrom(url: string, delay: number): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => {
resolve("来自 " + url + " 的数据");
}, delay);
});
}
function fetchWithRetry(url1: string, url2: string): Promise<string> {
return Promise.race<string>([
fetchFrom(url1, 1000),
fetchFrom(url2, 500)
]);
}
// 使用
fetchWithRetry("https://api1.com", "https://api2.com")
.then((data: string): void => {
console.log("最快返回:" + data);
})
.catch((error: Error): void => {
console.log("请求失败:" + error.message);
});
要点:
Promise.race返回最先完成的 Promise 结果- 较慢的请求仍会执行,只是结果被忽略
练习 6:重构回调地狱
将下面的回调地狱代码重构为 Promise 链:
step1((err: Error | null, result1: string | null) => {
if (err !== null) {
console.log("步骤1失败");
return;
}
step2(result1, (err: Error | null, result2: string | null) => {
if (err !== null) {
console.log("步骤2失败");
return;
}
step3(result2, (err: Error | null, result3: string | null) => {
if (err !== null) {
console.log("步骤3失败");
return;
}
console.log("最终结果:" + result3);
});
});
});
点击查看答案
// 假设 step1/step2/step3 已有 Promise 版本
step1Promise()
.then((result1: string): Promise<string> => {
return step2Promise(result1);
})
.then((result2: string): Promise<string> => {
return step3Promise(result2);
})
.then((result3: string): void => {
console.log("最终结果:" + result3);
})
.catch((error: Error): void => {
console.log("错误:" + error.message);
});
要点:
- 每个
then返回下一个 Promise,实现链式调用 - 用一个
catch捕获所有步骤的错误 - 代码从"金字塔"变成"链式",可读性大幅提升
练习 7:综合应用
实现一个数据加载函数:
- 先加载配置(1秒)
- 根据配置加载用户数据(1.5秒)
- 同时加载订单数据(2秒)
- 等用户数据和订单数据都准备好后,合并输出
点击查看答案
function loadConfig(): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("配置数据"), 1000);
});
}
function loadUsers(config: string): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("用户数据"), 1500);
});
}
function loadOrders(): Promise<string> {
return new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("订单数据"), 2000);
});
}
// 执行流程
loadConfig()
.then((config: string): Promise<string[]> => {
console.log("配置加载完成:" + config);
// 同时加载用户数据和订单数据
return Promise.all<string>([
loadUsers(config),
loadOrders()
]);
})
.then((results: string[]): void => {
console.log("用户数据:" + results[0]);
console.log("订单数据:" + results[1]);
console.log("合并输出完成");
})
.catch((error: Error): void => {
console.log("加载失败:" + error.message);
});
要点:
- 步骤 1 → 步骤 2 是顺序依赖(需要配置才能加载用户),用
then链 - 步骤 2 和步骤 3 是并行关系,用
Promise.all - 总耗时 ≈ 1秒(配置)+ max(1.5秒, 2秒) = 3秒
练习 8:思考讨论
- 为什么 ArkTS 选择单线程异步而不是多线程?
- Promise 相比回调函数的优势是什么?
- 什么情况下应该使用 Promise.all 而不是 Promise.race?
点击查看答案
问题 1:为什么 ArkTS 选择单线程异步而不是多线程?
- 简化编程模型:单线程避免了锁、竞态条件、死锁等并发问题
- I/O 密集型场景:UI 应用主要是网络、文件等 I/O 操作,单线程异步足够高效
- 事件循环机制:通过事件循环调度异步任务,I/O 等待期间 CPU 可以处理其他任务
- 与浏览器/JavaScript 生态一致:ArkTS 继承自 JavaScript,保持单线程模型
问题 2:Promise 相比回调函数的优势是什么?
- 代码扁平化:从嵌套金字塔变成链式结构,可读性更好
- 统一错误处理:一个
catch捕获链中所有错误,不用每个回调都写错误判断 - 可组合性:
Promise.all、Promise.race等方法方便组合多个异步操作 - 状态可追踪:Promise 有明确的三态(Pending/Fulfilled/Rejected),便于调试
问题 3:什么情况下应该使用 Promise.all 而不是 Promise.race?
-
Promise.all:需要所有任务的结果,例如:
- 加载多个资源(图片、数据)后才能渲染页面
- 并行执行多个独立计算,汇总结果
- 批量操作,需要全部成功才算成功
-
Promise.race:只需要最快完成的任务结果,例如:
- 请求超时控制
- 多个数据源竞争,用最快的
- 缓存优先 vs 网络优先
附录:完整代码示例
示例 1:Promise 基础用法
// 创建 Promise
function fetchData(): Promise<string> {
return new Promise<string>(
(resolve: (value: string) => void, reject: (reason: Error) => void): void => {
console.log("开始获取数据...");
setTimeout((): void => {
if (Math.random() > 0.3) {
resolve("数据获取成功");
} else {
reject(new Error("网络错误"));
}
}, 1000);
}
);
}
// 使用 Promise
fetchData()
.then((data: string): string => {
console.log("成功:" + data);
return "处理后的:" + data;
})
.then((processed: string): void => {
console.log(processed);
})
.catch((error: Error): void => {
console.log("失败:" + error.message);
})
.finally((): void => {
console.log("操作结束");
});
示例 2:Promise 组合使用
// 并行执行多个任务
function parallelTasks(): void {
let task1: Promise<string> = new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("任务1"), 1000);
});
let task2: Promise<string> = new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("任务2"), 2000);
});
let task3: Promise<string> = new Promise<string>((resolve: (value: string) => void): void => {
setTimeout((): void => resolve("任务3"), 1500);
});
// 等待全部完成
Promise.all<string>([task1, task2, task3])
.then((results: string[]): void => {
console.log("全部完成:" + results.join(", "));
});
// 取最快完成的
Promise.race<string>([task1, task2, task3])
.then((winner: string): void => {
console.log("最快完成:" + winner);
});
}
parallelTasks();

浙公网安备 33010602011771号