一个单向状态机 + 一条错误滑道 — then 返回的还是 Promise, async 函数只是把 then 链写成了顺序的样子
Promise 是一张只能兑现一次的票据: 要么resolve(带值), 要么reject(带原因), 落定后永久冻结。then 链是一串"拿到上一步结果做下一步"的接力 — 每一节返回的还是一个新 Promise, 所以能无限链下去; 任何一节出问题, 错误会像坐滑道一样越过所有成功回调, 直到被某个 catch 接住, 而接住后链又恢复正常。
async/await 没有引入任何新机制: async 函数永远返回 Promise, await 后面的代码等价于挂进 then 的微任务。它赢在把控制流写回了从上到下的顺序 — try/catch、for 循环、变量作用域全部回归同步直觉。理解了"票据 + 滑道", 组合器 all/race/allSettled/any 只是四种收票策略。
new Promise((res) => { res(1); res(2); console.log('after'); }) .then(v => log(v)); // 输出: after → 1 (第二次 res 无效, after 先同步跑)
console.log('before'); new Promise(() => console.log('executor')); console.log('after'); // → before executor after (executor 是同步的)
Promise.resolve(1)
.then(v => v + 1)
.then(v => log(v)); // → 2 接力传值p.then(() => delay(100)) // 返回 promise .then(() => log('100ms 后')); // 等它落定才跑
Promise.reject('e') .then(() => log('跳过我')) .catch(e => '恢复:' + e) .then(v => log(v)); // → 恢复:e
fetchA() .catch(recoverA) // 只管 fetchA .then(useData) .catch(finalLog); // 管 recoverA 之后的全部
loading = true; await fetchX().finally(() => loading = false); // 关键: 成功失败都关 loading, 且不影响返回值
const rs = await Promise.allSettled([a, b, c]); rs.filter(r => r.status === 'rejected') // 收集失败 .forEach(r => log(r.reason));
const fastest = await Promise.race([cdn1(), cdn2()]); const ok = await Promise.any([backup1(), backup2()]);
async function f() { return 5; } f() instanceof Promise; // → true f().then(log); // → 5
await 42 → 42), 但仍多一个微任务 tick; 热循环里别滥 await。 const x = await 42; // → 42 合法但没意义 // await p 的 p 若已落定, 只剩一次微任务开销
AbortSignal, 传进 fetch/事件。 const ac = new AbortController(); fetch(url, { signal: ac.signal }); ac.abort(); // 关键: 请求真正取消, abort 事件触发
第三方接口偶发 30s 不响应拖死页面。超时只是"不等了", 还要把慢请求真正掐断释放连接:
async function withTimeout(ms) { const ac = new AbortController(); const t = setTimeout(() => ac.abort(), ms); try { return await fetch(url, { signal: ac.signal }); } catch (e) { if (e.name === 'AbortError') throw new Error('TIMEOUT'); throw e; } finally { clearTimeout(t); } }
用 race 只解决"不等", 请求还在跑占连接; abort 才是真取消, P99 尾部从 30s 消失。
全量 Promise.all 会瞬间打爆下游, 手写 mapLimit 用"工位"控制并发:
async function mapLimit(items, limit, fn) { const results = new Array(items.length); let i = 0; async function worker() { while (i < items.length) { // 关键: 抢下一个工位 const idx = i++; results[idx] = await fn(items[idx]); } } await Promise.all(Array.from({ length: limit }, worker)); return results; } await mapLimit(urls, 8, download);
网络抖动该重试, 4xx 业务错重试也是白打。区分错误类型 + 退避 + 上限:
async function retry(fn, { times = 3, base = 200 } = {}) { for (let i = 0; ; i++) { try { return await fn(); } catch (e) { if (i >= times - 1 || !e.retryable) throw e; // 关键: 白名单 await new Promise(r => setTimeout(r, base * 2 ** i + Math.random() * 100)); } } } // 5xx/网络错标 retryable: true; 4xx 校验错不重试
大盘页同时拉 5 个数据源, 一个挂了不该整页 500。收集成败明细, 失败的模块降级展示:
const results = await Promise.allSettled([sales(), inventory(), ads()]); const data = {}, failed = []; for (const [i, r] of results.entries()) { if (r.status === 'fulfilled') data[sources[i].name] = r.value; else failed.push({ name: sources[i].name, err: String(r.reason) }); } logger.warn('partial failure', { failed }); // 可观测而非静默
老代码 fs.readFile 套三层, 错误要在每层处理。迁移后 try/catch 一处收口:
// 迁移前: read(a, (e1, a) => read(b(a), (e2, b) => ...)) 三层嵌套 // 迁移后: async function pipeline() { try { const a = await readFileA(); const b = await readFileB(a); // 关键: 依赖前一步的顺序逻辑 return merge(a, b); } catch (e) { // 三步的错误一处收口 log.error('pipeline', e); throw e; } }
热点 key 失效瞬间, 并发 100 个请求同时回源。让它们共享同一个 in-flight Promise:
const inflight = new Map(); async function getSingleFlight(key) { if (cache.has(key)) return cache.get(key); if (inflight.has(key)) return inflight.get(key); // 关键: 搭车 const p = loadFromDb(key) .then(v => { cache.set(key, v); return v; }) .finally(() => inflight.delete(key)); inflight.set(key, p); return p; }
击穿瞬间 DB 回源 QPS 从 100 → 1, Promise 作为"可共享的未来值"的精髓用法。
同一账户的扣款请求并发到达, 读-改-写交错导致余额错乱。用 Promise 链把写操作收敛成串行:
class WriteQueue { #tail = Promise.resolve(); // 串行化的链尾 enqueue(task) { const p = this.#tail.then(task); // 关键: 排在上一单之后 this.#tail = p.catch(() => {}); // 出错不断链 return p; } } const q = new WriteQueue(); q.enqueue(() => debit(accountId, 100));
SDK 抛的错五花八门, 上层没法分支。封装层把一切翻译成业务错误类型:
async function callPay(order) { try { return { ok: true, data: await sdk.pay(order) }; } catch (e) { if (e.code === 'INSUFFICIENT_FUNDS') return { ok: false, kind: 'NO_MONEY' }; if (e.name === 'TimeoutError') return { ok: false, kind: 'TIMEOUT', retryable: true }; logger.error('pay unknown', e); return { ok: false, kind: 'UNKNOWN' }; // 上层永远拿到可分支的形状 } }
DB 连接初始化有并发窗口: 缓存Promise 本身而不是结果, 天然防并发重复初始化:
let connP = null; function getConn() { if (!connP) { connP = createConn().catch(e => { connP = null; throw e; }); // 关键: 失败要清掉缓存的 rejected promise, 否则永远失败 } return connP; } const c = await getConn();
等 WebSocket "ready" 消息再继续, 同时防对方永远不来。once + race 组合:
function waitFor(emitter, event, ms) { return Promise.race([ new Promise(res => emitter.once(event, res)), new Promise((_, rej) => setTimeout(() => rej(new Error('event timeout')), ms)), ]); } await waitFor(ws, 'ready', 5000); // 拿到 ready 或 5s 报超时
// 错: new Promise((res, rej) => { if (x) res(1); }); // x 假时悬挂 // 对: if (x) res(1); else rej('no x');
// 错: p.then(d => { save(d); }).then(r => log(r)); // r 是 undefined // 对: p.then(d => save(d)).then(r => log(r));
Promise.all, 要限流用 mapLimit。 // 错: for (id of ids) await fetch(id); // 100×200ms=20s // 对: await Promise.all(ids.map(id => fetch(id))); // ~200ms
allSettled。 // 错: await Promise.all([a, b, c]); // b 挂全挂 // 对: await Promise.allSettled([a, b, c]);
AbortController 真取消。 // 错: Promise.race([fetch(u), timeout(1000)]); // fetch 还在跑 // 对: fetch(u, { signal: ac.signal }) + setTimeout(() => ac.abort(), 1000)
// 错: const u = getUser(); u.name; // undefined // 对: const u = await getUser(); u.name;
// 错: new Promise((res, rej) => setTimeout(() => { throw 1; })); // 对: new Promise((res, rej) => setTimeout(() => rej(1)));
// 错: new Promise(res => fetchX().then(res)); // 对: fetchX(); // 它本来就是 Promise
// 错: res(v); startHeavyWork(); // 还在干活 // 对: return res(v);
p.catch(e => { log(e); throw e; }) // 记录后继续向上滑 .catch(final); // 这里还能接到
// 错: p.finally(() => 'oops') // 链值被覆盖成 'oops' // 对: p.finally(() => cleanup()) // 返回 promise 会被等待
.catch。 // 错: p.then(v => risky(v), e => fix(e)); // risky 抛错没人接 // 对: p.then(v => risky(v)).catch(e => fix(e));
Promise.race([]); // → 永远 pending! // 对: if (!list.length) return default; else Promise.race(list)
connP = create().catch(e => { connP = null; throw e; });
// 关键: 自愈, 下次重新初始化process.on('unhandledRejection', (r) => report(r)); // 但兜底不是 excuse: 每个 await 链要有明确错误去向
for...of 串行或 mapLimit 并发。 // 错: list.forEach(async x => await send(x)); // 立刻全部发出 // 对: for (const x of list) await send(x);
// 对: const winner = ps.length ? Promise.race(ps) : Promise.resolve(null);
btn.onclick = async () => { try { await save(); } catch (e) { show(e); } // 自包错误 };
// 错: p.then(d => JSON.parse(huge)) // 阻塞 300ms // 对: worker.parse(huge) 或切片处理
// 错: const a = await f1(); const b = await f2(); // 串行 // 对: const pa = f1(), pb = f2(); const a = await pa, b = await pb;