惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - Franky
N
Netflix TechBlog - Medium
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
腾讯CDC
G
Google Developers Blog
Martin Fowler
Martin Fowler
Microsoft Security Blog
Microsoft Security Blog
Recent Announcements
Recent Announcements
爱范儿
爱范儿
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
aimingoo的专栏
aimingoo的专栏
有赞技术团队
有赞技术团队
Jina AI
Jina AI
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
美团技术团队
WordPress大学
WordPress大学
阮一峰的网络日志
阮一峰的网络日志

博客园 - thammer

03 — std::vector 进阶篇 02 — 迭代器基础 01 — std::vector 基础篇 Day 16 - 读懂 TypeScript 声明文件(.d.ts) Day 15 - ArkTS 模块系统 Day 14 - ArkTS 错误处理 Day 13 - ArkTS 类型系统进阶 Day 12 - 泛型与阶段三总结 Day 11 - Interface 与类型契约 Day 10 - 继承与多态 Day 09 - Class 基础 Day 08 - 函数进阶与阶段总结 Day 07 - 函数类型与高阶函数 Day 06 - 函数基础 Day 05 - 数组和元组深入 Day 04 - 循环与迭代 Day 03 - 条件控制 Day 02 - 运算符与表达式 Day 01 - 变量与类型基础 Day 00 - 环境准备 C++虚函数 C++ 多继承 C++成员访问权限以及继承权限 C++ 成员初始化机制总结笔记
Day 17:Promise 基础
thammer · 2026-04-21 · via 博客园 - thammer

学习目标:理解异步编程的必要性,掌握 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);
            });
        });
    });
});

问题

  1. 代码横向发展,形成"金字塔"结构
  2. 每一层都要处理错误,重复代码多
  3. 逻辑嵌套深,可读性差
  4. 错误处理分散,容易遗漏

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;
        }
        // ... 更多嵌套
    });
});

引出:我们需要一种更好的异步模式,能够:

  1. 扁平化代码结构(不要嵌套)
  2. 集中处理错误(不要每个回调都写错误判断)
  3. 更优雅地组合多个异步操作

这就是 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  │
│  (失败)   │
└───────────┘

状态规则

  1. Pending:初始状态,操作正在进行中
  2. Fulfilled:操作成功完成,有结果值
  3. Rejected:操作失败,有错误原因
  4. 不可变性:状态一旦改变,不能再变(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 状态变为 Fulfilled
    • reject:失败时调用,将 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);
    }
);

重要规则

  1. resolve 和 reject 只能调用一次,多次调用无效
  2. reject 通常传递 Error 对象(便于错误追踪)
  3. 执行器函数中的同步错误会自动转为 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);
    });

链式调用的优势

  1. 代码扁平化,不再是金字塔
  2. 错误处理集中,一个 catch 捕获所有错误
  3. 每一步的逻辑更清晰

链式调用简写

// 当箭头函数直接返回 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);
    });

行为规则

  1. 所有 Promise 都成功 → then() 接收结果数组
  2. 任意一个 Promise 失败 → catch() 捕获第一个错误
  3. 总耗时 = 最慢的那个 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 中使用受限

原因如下:

  1. 返回联合类型allSettled 返回 PromiseSettledResult<T>[],其中 PromiseSettledResult 是联合类型(fulfilled | rejected)
  2. ArkTS 不支持解构赋值:无法方便地提取 statusvalue
  3. 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> 显式声明泛型为 void
  • setTimeout 可以直接传 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秒)
  2. 根据配置加载用户数据(1.5秒)
  3. 同时加载订单数据(2秒)
  4. 等用户数据和订单数据都准备好后,合并输出
点击查看答案
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:思考讨论

  1. 为什么 ArkTS 选择单线程异步而不是多线程?
  2. Promise 相比回调函数的优势是什么?
  3. 什么情况下应该使用 Promise.all 而不是 Promise.race?
点击查看答案

问题 1:为什么 ArkTS 选择单线程异步而不是多线程?

  • 简化编程模型:单线程避免了锁、竞态条件、死锁等并发问题
  • I/O 密集型场景:UI 应用主要是网络、文件等 I/O 操作,单线程异步足够高效
  • 事件循环机制:通过事件循环调度异步任务,I/O 等待期间 CPU 可以处理其他任务
  • 与浏览器/JavaScript 生态一致:ArkTS 继承自 JavaScript,保持单线程模型

问题 2:Promise 相比回调函数的优势是什么?

  • 代码扁平化:从嵌套金字塔变成链式结构,可读性更好
  • 统一错误处理:一个 catch 捕获链中所有错误,不用每个回调都写错误判断
  • 可组合性Promise.allPromise.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();