第5章:集合、异步与错误边界
约 4 分钟 · 更新于 2026-09-01
第5章:集合、异步与错误边界
本章把类型放进真实控制流:数组、Map、Set、迭代器、Promise、async/await 与错误处理。重点是让“可能失败、可能为空、可能并发”成为显式契约。
一、数组方法与类型推断
ts
interface Order {
id: string;
amount: number;
paid: boolean;
}
const orders: Order[] = [
{ id: "A", amount: 100, paid: true },
{ id: "B", amount: 80, paid: false }
];
const paidTotal = orders
.filter(order => order.paid)
.map(order => order.amount)
.reduce((sum, amount) => sum + amount, 0);
回调上下文通常能推断参数类型。复杂变换可抽成命名函数,提高错误定位和复用性。
二、Map 与普通对象
ts
const priceByRoute = new Map<string, number>();
priceByRoute.set("HKG-NRT", 1200);
const price = priceByRoute.get("HKG-NRT"); // number | undefined
Map 的优势:
- 键不限于字符串。
- size、遍历和增删语义明确。
- 不受对象原型键影响。
普通对象适合 JSON 数据、固定字段和简单记录;Record<K, V> 适合有限键集合。
三、Set、WeakMap 与 WeakSet
ts
const airports = new Set<string>(["HKG", "NRT", "HKG"]);
Set 表达唯一值集合。WeakMap 和 WeakSet 的键必须是对象,且不阻止垃圾回收,适合给对象附加缓存或元数据;它们不可枚举,不适合作为普通数据容器。
四、迭代协议与生成器
任何实现 Symbol.iterator 的对象都可被 for...of 遍历。
ts
function* range(start: number, end: number): Generator<number> {
for (let value = start; value <= end; value += 1) {
yield value;
}
}
for (const value of range(1, 3)) {
console.log(value);
}
生成器按需产生值,适合惰性序列和状态机,但不要把简单数组逻辑过度改写成生成器。
五、Promise 类型
ts
function loadUser(id: string): Promise<User> {
return fetch(`/api/users/${id}`).then(async response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json() as User;
});
}
这里的 as User 仍没有运行时验证。更可靠的边界应先解析为 unknown,再验证。
Promise 的常见组合:
ts
const [user, orders] = await Promise.all([
loadUser("1"),
loadOrders("1")
]);
- Promise.all:全部成功,任一失败则整体拒绝。
- Promise.allSettled:等待全部完成并保留每项结果。
- Promise.race:第一个完成或失败的结果。
- Promise.any:第一个成功结果,全部失败时拒绝。
六、async/await
async 函数始终返回 Promise:
ts
async function getCount(): Promise<number> {
return 3;
}
顺序执行:
ts
const user = await loadUser("1");
const orders = await loadOrders(user.id);
无依赖任务应并发启动:
ts
const userPromise = loadUser("1");
const configPromise = loadConfig();
const [user, config] = await Promise.all([userPromise, configPromise]);
七、异步取消与超时
Promise 本身没有通用取消语义。Web API 常用 AbortController:
ts
async function fetchJson(url: string, timeoutMs: number): Promise<unknown> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} finally {
clearTimeout(timer);
}
}
类型只能描述 signal,不能替你设计取消后的状态恢复。
八、错误类型的现实限制
JavaScript 可以 throw 任意值,所以 catch 变量应按 unknown 处理:
ts
try {
await doWork();
} catch (error: unknown) {
if (error instanceof Error) {
console.error(error.message);
} else {
console.error("未知错误", error);
}
}
自定义错误类:
ts
class ValidationError extends Error {
constructor(
message: string,
public readonly field: string
) {
super(message);
this.name = "ValidationError";
}
}
九、Result 模式
对可预期业务失败,可用判别联合避免异常控制流:
ts
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
function parsePort(input: string): Result<number, string> {
const value = Number(input);
if (!Number.isInteger(value) || value < 1 || value > 65535) {
return { ok: false, error: "端口范围应为 1~65535" };
}
return { ok: true, value };
}
选择原则:
- 程序不变量破坏或无法恢复的异常:throw。
- 用户输入、业务拒绝等预期失败:Result 或明确返回值常更合适。
- 不要机械地把所有第三方异步 API 包装成自制 Result,保持项目一致性。
十、外部数据校验
ts
interface User {
id: number;
name: string;
}
function parseUser(value: unknown): User {
if (typeof value !== "object" || value === null) {
throw new ValidationError("用户必须是对象", "user");
}
const record = value as Record<string, unknown>;
if (typeof record.id !== "number") {
throw new ValidationError("id 必须是数字", "id");
}
if (typeof record.name !== "string") {
throw new ValidationError("name 必须是字符串", "name");
}
return { id: record.id, name: record.name };
}
大型项目通常采用 schema 校验库统一生成运行时校验和静态类型,避免手写守卫漂移。
十一、空值与集合查询
find、Map.get、数组索引都可能没有结果:
ts
const order = orders.find(item => item.id === "C"); // Order | undefined
if (!order) {
throw new Error("订单不存在");
}
不要通过非空断言掩盖:
ts
orders.find(item => item.id === "C")!.amount;
只有在结构性不变量已被前置逻辑证明时,非空断言才可作为局部说明。
十二、常见误区
- response.json() as User 被当作真实校验。
- 在 catch 中假设错误一定是 Error。
- 本可并行的 Promise 被连续 await。
- 忘记清理计时器、监听器和取消控制器。
- 对预期业务失败全部使用异常。
- 对不可恢复错误全部吞掉并返回空值。
- 使用 ! 掩盖集合查询可能为空。
- 把 WeakMap 当作可枚举数据表。
十三、实践练习
- 实现 parseUser(unknown) 并覆盖错误输入测试。
- 并行加载用户与配置,比较连续和并发等待。
- 为 fetch 增加超时与取消,确保 finally 清理计时器。
- 用 Result 模式实现端口解析。
- 使用 Promise.allSettled 汇总多个供应商请求结果。
十四、总结
- 集合 API 的返回类型会显式暴露“可能不存在”。
- async 函数返回 Promise,类型不会自动设计并发、取消和超时。
- catch 输入应按 unknown 收窄。
- 预期失败可以用判别联合表达,不必全部抛异常。
- 外部数据进入可信领域模型之前必须执行运行时验证。
- 类型安全的异步代码仍需要资源清理、容量和错误策略。
请继续阅读:第6章:模块化工程。
原始资料引用