Promise 与 async/await 心智模型
专栏:TypeScript 与 Node 地基 · 第 4 / 13 篇:::info 学习目标 完成本篇后你能够:用”稍后兑现的值”正确推理 Promise;熟练使用 async/await 与 Promise 组合器;避免五个最常见的异步陷阱。 前置:第 1~2 篇完成。预计时长:60 分钟。 :::
Node 是单线程的:同一时刻只执行一段代码。所有耗时操作(网络、磁盘、定时器)都不阻塞,而是”发起后先干别的,完成后回来通知你”。Promise 就是那张”完成通知单”。
心智模型:Promise 是稍后兑现的值
//Promise<T> 读作:"将来会有一个 T"
const response: Promise<Response> = fetch('https://api.example.com/v1/models')
fetch 立刻返回(请求还在路上),返回的 Promise 承诺”完成后给你一个 Response”。等它的方式:
// 方式一:await——只在 async 函数里可用,写法最像同步代码
const response = await fetch(url)
const data = await response.json()
// 方式二:.then 链——等价,但回调风格
fetch(url).then(r => r.json()).then(data => { ... })
await 的语义:暂停这个 async 函数(不是暂停整个程序!),把控制权还给事件循环,Promise 兑现后从这一行继续。单线程却能让几百个请求并发,靠的就是这个。
Promise 的生命周期
三个组合器:并发编排的全部
三个组合器:并发编排的全部
agent 循环里最常见的三种并发形态:
await Promise.all([p1, p2, p3]) // 全部完成才继续;一个失败全失败
await Promise.allSettled([p1, p2]) // 等全部结束,不抛错;逐个看结果
await Promise.race([p1, timeout(5000)])// 谁先完成用谁——超时控制的标准写法
dsh 的工具并发用 FuturesOrdered(Rust 版的按序并发),TS 里对应的就是受控的 Promise.all:先全部发起,再按提交顺序收结果。第 10 篇的 mini-agent 里会真的写一遍。
async 函数的规则
async function run(): Promise<string> { // async 函数永远返回 Promise
const r = await step1()
return await step2(r) // 返回值被自动包进 Promise
}
const p = run() // ← 调用的瞬间函数体已开始执行(到第一个 await 处让出)
const s = await p // 等它完成
最容易误解的一点:async 函数被调用时立即开始执行,直到第一个 await 才让出控制权。所以”启动一个后台任务”不需要额外语法——调用 async 函数但不 await(注意接住异常)。
五个陷阱
- 忘了 await:
save()返回 Promise,你不等它,程序继续往下跑——错误会变成”未处理的 rejection”,写库可能根本没完成; - 循环里串行 await:
for (const u of users) await fetch(u)是一个接一个;要并发用Promise.all(users.map(fetch)); - await 在 forEach 里无效——
forEach不认识 Promise,改用for...of(串行)或Promise.all(map())(并发); - 未处理的 rejection:发起 Promise 却不 await 也不接错误,进程可能被击穿。后台任务用
.catch(...)兜底; - 在非 async 函数里用 await——语法错误;把外层函数标成 async 即可。
与 dsh 的对照
dsh 的 DoRequest/DoResponse、execute(args, exec) 全是 async 函数;工具并发调度器(第 9 篇)本质是对一批 Promise 做受控的 Promise.all——只把并发度限制在 10 以内、按模型顺序提交结果。读懂本篇,那段调度器的每一行都在你的语言能力范围内。
随堂练习(带验收标准)
- 写
fetchAll(urls: string[])并发抓取,全部完成后返回状态码数组。验收:10 个 URL 总耗时 ≈ 最慢的那个(而非它们的和); - 加一个
timeout(p, ms)帮助函数(Promise.race+ setTimeout reject)。验收:慢请求被 5 秒超时打断并返回错误; - 把练习 1 改成”最多同时 3 个并发”(提示:分批)。验收:并发峰值从未超过 3。