第4章:Promise —— 异步的承诺
第4章:Promise —— 异步的承诺
4.1 什么是 Promise?
Promise(承诺)是对一个异步操作最终完成或失败的对象表示。它不是回调的替代品,而是对回调模式的抽象升级。
类比:Promise 就像一张外卖订单——你下单后得到一个订单号(Promise 对象),你可以随时查看订单状态,订单完成后你会收到食物(结果),或者被告知配送失败(错误)。
三种状态
then()
Pending ─────────────► Fulfilled (已完成)
│ │ value
│ │
│ catch() │
└───────────────► Rejected (已拒绝)
reason
| 状态 | 英文 | 说明 | 能否转换 |
|---|---|---|---|
| 待定 | Pending | 初始状态,既未完成也未拒绝 | → Fulfilled 或 Rejected |
| 已完成 | Fulfilled | 操作成功完成,有结果值 | 不可再变 |
| 已拒绝 | Rejected | 操作失败,有错误原因 | 不可再变 |
关键规则:状态一旦改变,就不可逆(immutable)。一个 Promise 只能从 Pending 变为 Fulfilled 或 Rejected,之后不会再改变。
4.2 创建 Promise
JavaScript
// 基本创建
const promise = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('操作成功'); // → Fulfilled
} else {
reject(new Error('操作失败')); // → Rejected
}
}, 1000);
});
// 使用
promise
.then(value => console.log(value))
.catch(err => console.error(err.message));
包装已有回调
// 将 Node.js 回调风格转为 Promise
const { promisify } = require('util');
const fs = require('fs');
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
// 手动包装
function readFileManual(path, encoding) {
return new Promise((resolve, reject) => {
fs.readFile(path, encoding, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
// Node.js 16+ 内置的 promise API
const fsPromises = require('fs/promises');
await fsPromises.readFile('/path', 'utf8');
Python
import asyncio
async def fetch_data():
await asyncio.sleep(1) # 模拟 I/O
return {'status': 'ok', 'data': [1, 2, 3]}
# Python 的协程本身就是类似 Promise 的概念
result = asyncio.run(fetch_data())
Java
// CompletableFuture 是 Java 的 Promise 实现
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 异步操作
return fetchDataFromDB();
});
future
.thenApply(data -> processData(data)) // 转换
.thenAccept(result -> saveResult(result)) // 消费
.exceptionally(err -> { // 错误处理
log.error("Failed:", err);
return null;
});
Rust
use tokio;
#[tokio::main]
async fn main() {
let result = fetch_data().await;
println!("结果: {:?}", result);
}
async fn fetch_data() -> Result<String, reqwest::Error> {
let resp = reqwest::get("https://api.example.com").await?;
let body = resp.text().await?;
Ok(body)
}
4.3 链式调用(Chaining)
链式调用是 Promise 最强大的特性之一。每次 .then() 都会返回一个新的 Promise,从而形成调用链。
链式调用的规则
| 返回类型 | 新 Promise 的状态 | 新 Promise 的值 |
|---|---|---|
| 普通值 | Fulfilled | 该值 |
| 新 Promise | 由该 Promise 决定 | 由该 Promise 决定 |
| 抛出异常 | Rejected | 该异常 |
// ❌ 回调地狱
getUser(userId, (err, user) => {
getProduct(productId, (err, product) => {
createOrder(user, product, (err, order) => {
sendNotification(user.email, order, (err) => {
console.log('完成');
});
});
});
});
// ✅ Promise 链式调用
getUser(userId)
.then(user => getProduct(productId).then(product => ({ user, product })))
.then(({ user, product }) => createOrder(user, product))
.then(order => sendNotification(user.email, order))
.then(() => console.log('完成'))
.catch(err => console.error('失败:', err));
扁平化技巧
// 使用 async/await 是最直接的方式(第 5 章详述)
async function placeOrder(userId, productId) {
const user = await getUser(userId);
const product = await getProduct(productId);
const order = await createOrder(user, product);
await sendNotification(user.email, order);
return order;
}
4.4 错误传播
Promise 的错误传播机制是它相比回调最大的优势之一。
错误冒泡
Promise.resolve('start')
.then(value => {
throw new Error('步骤 1 失败');
})
.then(value => {
console.log('这不会执行');
})
.then(value => {
console.log('这也不会执行');
})
.catch(err => {
// 错误会一直冒泡,直到被某个 catch 捕获
console.error('捕获到:', err.message); // "步骤 1 失败"
});
catch vs then 的第二个参数
// 方式一:使用 .catch()
promise
.then(onFulfilled)
.catch(onRejected); // 捕获前面所有步骤的错误
// 方式二:使用 .then() 的第二个参数
promise.then(onFulfilled, onRejected); // 只捕获 promise 本身的错误
// 区别:catch 能捕获 onFulfilled 中抛出的错误
promise
.then(value => {
throw new Error('then 中的错误');
})
.catch(err => {
console.error('能捕获:', err); // ✅
});
promise.then(
value => { throw new Error('then 中的错误'); },
err => {
console.error('不能捕获:', err); // ❌ 只捕获 promise 本身的错误
}
);
最佳实践:始终使用
.catch()而非.then()的第二个参数,除非你有明确的理由。
未处理的拒绝(Unhandled Rejection)
// ⚠️ 忘记处理 rejection 会导致 unhandledRejection
const bad = Promise.reject(new Error('没有人处理我'));
// 正确做法:总是以 .catch() 结尾
const good = Promise.reject(new Error('我会被处理'))
.catch(err => console.error('已处理:', err.message));
// Node.js 15+ 默认将 unhandledRejection 视为致命错误
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的拒绝:', reason);
// 建议记录日志后优雅退出
process.exit(1);
});
4.5 并发组合方法
Promise 提供了多个静态方法来处理并发异步操作。
Promise.all — 全部成功
// 等待所有 Promise 都成功,任一失败则立即失败
const results = await Promise.all([
fetch('/api/users'),
fetch('/api/products'),
fetch('/api/orders'),
]);
| 特性 | 说明 |
|---|---|
| 成功条件 | 所有 Promise 都 Fulfilled |
| 失败条件 | 任一 Promise Rejected |
| 返回值 | 所有结果的数组,顺序与输入一致 |
| 短路行为 | 任一失败立即返回,不等待其余 |
Promise.allSettled — 全部完成(ES2020)
// 等待所有 Promise 都结束(无论成功或失败)
const results = await Promise.allSettled([
fetch('/api/users'),
fetch('/api/products'),
fetch('/api/orders'),
]);
// 每个结果对象:
// { status: 'fulfilled', value: ... }
// { status: 'rejected', reason: ... }
| 特性 | 说明 |
|---|---|
| 成功条件 | 永远不会 reject(总是等待所有完成) |
| 返回值 | 每个 Promise 的结果状态和值 |
| 适用场景 | 需要所有结果,不因单个失败而中断 |
Promise.race — 竞速
// 返回第一个完成的 Promise(无论成功或失败)
const fastest = await Promise.race([
fetch('/api/fast-server'),
fetch('/api/slow-server'),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), 5000)
),
]);
| 特性 | 说明 |
|---|---|
| 返回条件 | 第一个 Fulfilled 或 Rejected 的 Promise |
| 常见用途 | 超时控制、多源竞速 |
Promise.any — 任一成功(ES2021)
// 返回第一个成功的 Promise,全部失败才拒绝
const result = await Promise.any([
fetch('/api/server-1'),
fetch('/api/server-2'),
fetch('/api/server-3'),
]);
// 如果全部失败,抛出 AggregateError
try {
const result = await Promise.any(failingPromises);
} catch (err) {
console.error(err.errors); // 所有错误的数组
}
组合方法对比
| 方法 | 成功条件 | 失败条件 | 返回值 | 等价类比 |
|---|---|---|---|---|
all | 全部成功 | 任一失败 | 结果数组 | AND |
allSettled | 永不 reject | 永不 reject | 状态数组 | 等待 |
race | 任一完成 | 任一完成 | 第一个结果 | OR |
any | 任一成功 | 全部失败 | 第一个成功值 | OR(忽略错误) |
4.6 Promise.finally
finally() 在 Promise 结束后执行,无论成功或失败。
function fetchData(url) {
showLoadingSpinner();
return fetch(url)
.then(res => res.json())
.catch(err => {
showErrorNotification(err);
throw err; // 重新抛出,让调用方也知道失败了
})
.finally(() => {
hideLoadingSpinner(); // 无论成功失败都隐藏加载动画
});
}
finally 的特性
const p = Promise.resolve(42);
p.finally(() => {
console.log('finally 执行');
// 1. 没有返回值 → 原始值 42 传递给下一个 then
// 2. 返回一个普通值 → 同上,值被忽略
// 3. 返回一个 Promise → 等待它完成,但值仍被忽略
// 4. 抛出异常 → 新的 Rejected Promise
}).then(value => {
console.log(value); // 42
});
4.7 实用工具函数
超时控制
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error(`操作超时 (${ms}ms)`)), ms)
),
]);
}
// 使用
try {
const data = await withTimeout(fetch('/api/slow'), 3000);
} catch (err) {
console.error(err.message); // "操作超时 (3000ms)"
}
重试
async function withRetry(fn, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try {
return await fn();
} catch (err) {
if (i === retries - 1) throw err;
await new Promise(r => setTimeout(r, delay * Math.pow(2, i)));
}
}
}
// 使用
const data = await withRetry(() => fetch('/api/unreliable'), 3, 1000);
限流(并发控制)
async function parallelLimit(tasks, limit) {
const results = [];
const executing = new Set();
for (const [i, task] of tasks.entries()) {
const p = task().then(result => {
executing.delete(p);
return result;
});
executing.add(p);
results[i] = p;
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// 使用:最多同时 3 个请求
const tasks = urls.map(url => () => fetch(url));
const results = await parallelLimit(tasks, 3);
4.8 业务场景:API 网关聚合
场景
前端一个页面需要同时调用 5 个后端 API 获取数据,需要:
- 并发请求所有 API(加速)
- 单个 API 超时不阻塞整体
- 部分失败时返回已有数据
async function aggregateAPIs(requests) {
const results = {};
const settled = await Promise.allSettled(
requests.map(async ({ key, url, timeout = 3000 }) => {
const response = await withTimeout(fetch(url), timeout);
const data = await response.json();
return { key, data };
})
);
for (const result of settled) {
if (result.status === 'fulfilled') {
results[result.value.key] = result.value.data;
} else {
console.warn(`API 失败:`, result.reason);
}
}
return results;
}
// 使用
const data = await aggregateAPIs([
{ key: 'user', url: '/api/user/123' },
{ key: 'orders', url: '/api/orders?user=123' },
{ key: 'profile', url: '/api/profile/123' },
{ key: 'settings',url: '/api/settings/123' },
{ key: 'notifications', url: '/api/notifications/123', timeout: 5000 },
]);
4.9 各语言 Promise 对比
| 语言 | 类名 | 创建 | 组合方法 |
|---|---|---|---|
| JavaScript | Promise | new Promise() | all, allSettled, race, any |
| Python | asyncio.Future | loop.create_future() | asyncio.gather, asyncio.wait |
| Java | CompletableFuture | supplyAsync() | allOf, anyOf, thenCombine |
| Rust | Future (trait) | async {} | join!, select!, try_join! |
| C++ | std::future | std::async() | 无内置,需第三方库 |
| Go | 无内置(使用 Channel) | make(chan T) | select |
4.10 本章小结
| 要点 | 说明 |
|---|---|
| Promise 状态 | Pending → Fulfilled / Rejected,不可逆 |
| 链式调用 | .then() 返回新 Promise,实现扁平化 |
| 错误传播 | 错误会冒泡,直到被 .catch() 捕获 |
| 并发组合 | all(AND)、race(竞速)、any(OR)、allSettled(等待) |
| finally | 无论成功失败都执行,适合清理操作 |
| 工具函数 | 超时、重试、限流是常见需求 |
下一章预告:Promise 虽然比回调好得多,但链式调用仍然不够直观。async/await 让异步代码看起来像同步代码——这是异步编程的"终极语法糖"。
扩展阅读
- MDN — Promise
- Java CompletableFuture
- Promises/A+ Specification — Promise 规范
- We have a problem with promises — 经典踩坑文章
- JavaScript Promises: an Introduction — Google 开发者