Promise 与 async/await 心智模型

专栏:TypeScript 与 Node 地基 · 第 4 / 13 篇
TypeScript异步Promise

:::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 的生命周期

图表(ts-promise-async.md)

三个组合器:并发编排的全部

三个组合器:并发编排的全部

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(注意接住异常)。

五个陷阱

  1. 忘了 awaitsave() 返回 Promise,你不等它,程序继续往下跑——错误会变成”未处理的 rejection”,写库可能根本没完成;
  2. 循环里串行 awaitfor (const u of users) await fetch(u) 是一个接一个;要并发用 Promise.all(users.map(fetch))
  3. await 在 forEach 里无效——forEach 不认识 Promise,改用 for...of(串行)或 Promise.all(map())(并发);
  4. 未处理的 rejection:发起 Promise 却不 await 也不接错误,进程可能被击穿。后台任务用 .catch(...) 兜底;
  5. 在非 async 函数里用 await——语法错误;把外层函数标成 async 即可。

与 dsh 的对照

dsh 的 DoRequest/DoResponseexecute(args, exec) 全是 async 函数;工具并发调度器(第 9 篇)本质是对一批 Promise 做受控的 Promise.all——只把并发度限制在 10 以内、按模型顺序提交结果。读懂本篇,那段调度器的每一行都在你的语言能力范围内。

随堂练习(带验收标准)

  1. fetchAll(urls: string[]) 并发抓取,全部完成后返回状态码数组。验收:10 个 URL 总耗时 ≈ 最慢的那个(而非它们的和);
  2. 加一个 timeout(p, ms) 帮助函数(Promise.race + setTimeout reject)。验收:慢请求被 5 秒超时打断并返回错误;
  3. 把练习 1 改成”最多同时 3 个并发”(提示:分批)。验收:并发峰值从未超过 3。

← 返回文章列表