TL;DR — 快速摘要
Promise 是 JavaScript 异步操作的基础。Promise 有三种状态:pending(等待)、fulfilled(已完成)、rejected(已拒绝)。 使用 async/await 编写可读性更强的异步代码; 使用 Promise.all 并行执行多个操作; 使用 try/catch 或 .catch() 捕获错误; 使用 Promise.race 实现超时; 使用 Promise.allSettled 在部分失败时仍获取全部结果。 本指南包含真实代码示例和常见错误的解决方案。
核心要点
- Promise 是单次使用的异步结果容器,状态不可逆
- async/await 是 Promise 链式调用的语法糖,本质相同
- Promise.all 快速失败;Promise.allSettled 等待全部完成
- 错误必须用 .catch() 或 try/catch 显式捕获,否则成为未处理的拒绝
- 并行执行比串行 await 快得多
- Promise.race 可实现超时和竞速场景
- 避免回调地狱、嵌套 Promise 和忘记 await 等常见错误
1. Promise 基础:状态与生命周期
在 Promise 诞生之前,JavaScript 使用回调函数处理异步操作。回调函数层层嵌套,形成臭名昭著的"回调地狱"。 ES6(2015)引入的 Promise 提供了一种结构化的方式来表示异步操作的最终完成(或失败)。
Promise 对象代表一个异步操作,它有三种互斥状态:
- Pending(等待):初始状态,操作尚未完成
- Fulfilled(已完成):操作成功完成,有一个结果值
- Rejected(已拒绝):操作失败,有一个错误原因
一旦 Promise 从 pending 状态过渡到 fulfilled 或 rejected,状态就"定格"了,永远不会再改变。 已完成或已拒绝的 Promise 统称为"已敲定(settled)"。
// Promise 的三种状态
const pending = new Promise(() => {}); // 永远等待
const fulfilled = Promise.resolve('success'); // 立即完成
const rejected = Promise.reject(new Error('fail')); // 立即拒绝
// 使用 .then() 和 .catch() 处理结果
fulfilled
.then(value => console.log('值:', value)) // 值: success
.catch(err => console.error('错误:', err));
rejected
.then(value => console.log('不会执行'))
.catch(err => console.error('捕获到:', err.message)); // 捕获到: fail.then()、.catch() 和 .finally()
Promise 的三个核心方法用于处理不同的结果:
fetch('https://api.example.com/users/1')
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json(); // 返回新 Promise
})
.then(user => {
console.log('用户名:', user.name);
return user;
})
.catch(error => {
// 捕获链上任何地方抛出的错误
console.error('请求失败:', error.message);
return null; // 可以恢复,返回默认值
})
.finally(() => {
// 无论成功还是失败都执行
// 适合清理操作:隐藏加载状态、关闭连接等
console.log('请求已完成');
});2. 创建 Promise:new Promise()、resolve 和 reject
使用 new Promise(executor) 创建 Promise。executor 函数接收两个参数:resolve(成功时调用)和 reject(失败时调用)。
// 基本用法
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 带结果的延迟
function delayedValue(value, ms) {
return new Promise(resolve => setTimeout(() => resolve(value), ms));
}
// 模拟随机成功/失败
function unreliableOperation() {
return new Promise((resolve, reject) => {
const success = Math.random() > 0.5;
setTimeout(() => {
if (success) resolve({ data: '操作成功', timestamp: Date.now() });
else reject(new Error('操作随机失败'));
}, 1000);
});
}
// 包装回调风格的 API
function readFileAsync(path) {
return new Promise((resolve, reject) => {
const fs = require('fs');
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
// 立即完成/拒绝的快捷方式
const ok = Promise.resolve(42);
const err = Promise.reject(new TypeError('类型错误'));3. Promise 链式调用
.then() 总是返回一个新的 Promise,这使得链式调用成为可能。 链中某一步返回的值会传递到下一个 .then();抛出的错误会传递到最近的 .catch()。
// 正确的链式调用(扁平化,非嵌套)
fetchUser(userId)
.then(user => fetchUserPosts(user.id)) // 返回新 Promise
.then(posts => fetchComments(posts[0].id))
.then(comments => renderComments(comments))
.catch(err => showError(err));
// 错误传播:错误会跳过 .then() 直到遇到 .catch()
Promise.resolve(1)
.then(v => { throw new Error('第2步出错'); })
.then(v => console.log('这里不会执行'))
.then(v => console.log('这里也不会执行'))
.catch(err => console.log('捕获到:', err.message)); // 捕获到: 第2步出错
// 在 .catch() 后恢复链式调用
fetchData()
.then(processData)
.catch(err => {
console.warn('使用缓存数据,原因:', err.message);
return getCachedData(); // 返回备用值,链继续执行
})
.then(data => renderUI(data)); // 无论原始请求是否成功都会执行4. async/await:现代异步语法
async/await 是 ES2017 引入的语法糖,让异步代码看起来像同步代码。async 函数始终返回一个 Promise;await 暂停函数执行直到 Promise 敲定。
// async 函数基本用法
async function fetchUserProfile(userId) {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const user = await response.json();
return user;
}
// try/catch 错误处理(等同于 .catch())
async function getUserSafely(userId) {
try {
const user = await fetchUserProfile(userId);
console.log('用户:', user.name);
return user;
} catch (error) {
if (error.message.includes('404')) {
console.warn('用户不存在');
return null;
}
throw error; // 重新抛出无法处理的错误
} finally {
console.log('fetchUserSafely 执行完毕');
}
}
// 顶层 await(ES2022,在 ES 模块中可用)
const config = await loadConfig('./config.json');
console.log('配置已加载:', config.version);串行执行 vs 并行执行
这是 async/await 最常见的性能陷阱:连续的 await 是串行执行的, 如果操作之间互不依赖,应该并行执行。
// 慢:串行执行,总耗时 = 300ms + 200ms + 400ms = 900ms
async function slowApproach() {
const users = await fetchUsers(); // 等待 300ms
const products = await fetchProducts(); // 再等待 200ms
const orders = await fetchOrders(); // 再等待 400ms
return { users, products, orders };
}
// 快:并行执行,总耗时 ≈ max(300, 200, 400) = 400ms
async function fastApproach() {
const [users, products, orders] = await Promise.all([
fetchUsers(),
fetchProducts(),
fetchOrders(),
]);
return { users, products, orders };
}
// 数组并行处理
async function processAllItems(items) {
// 正确:并行处理所有项
const results = await Promise.all(items.map(item => processItem(item)));
// 错误:for...of 中的 await 是串行的
// for (const item of items) {
// await processItem(item); // 逐个处理,很慢!
// }
return results;
}5. Promise 组合器:all、allSettled、race、any
JavaScript 提供了四个静态方法来处理多个 Promise,每个方法有不同的"等待策略":
| 方法 | 完成条件 | 失败条件 | 典型使用场景 |
|---|---|---|---|
| Promise.all | 全部完成 | 任意一个拒绝 | 需要所有结果才能继续 |
| Promise.allSettled | 全部敲定(无论结果) | 永不拒绝 | 批量操作,需要全部状态 |
| Promise.race | 第一个敲定 | 第一个拒绝 | 超时、取第一个响应 |
| Promise.any | 第一个完成 | 全部拒绝 | 多个备用源,取最快成功的 |
// Promise.all — 全部成功才完成,任一失败立即失败
const [user, posts, friends] = await Promise.all([
fetchUser(id),
fetchPosts(id),
fetchFriends(id),
]);
// Promise.allSettled — 等待全部,获取每个的状态
const results = await Promise.allSettled([
fetchPrimaryAPI(),
fetchSecondaryAPI(),
fetchTertiaryAPI(),
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.warn('失败:', result.reason.message);
}
});
// Promise.race — 第一个敲定的(成功或失败)
const firstResponse = await Promise.race([
fetchFromServer1(),
fetchFromServer2(),
fetchFromServer3(),
]);
// Promise.any — 第一个成功的(忽略拒绝直到全部拒绝)
try {
const fastest = await Promise.any([
fetchFromCDN1(),
fetchFromCDN2(),
fetchFromCDN3(),
]);
console.log('最快成功:', fastest);
} catch (aggregateError) {
// 所有 Promise 都拒绝时抛出 AggregateError
console.error('所有来源都失败:', aggregateError.errors);
}6. 错误处理模式
正确处理 Promise 错误至关重要。未处理的 Promise 拒绝会导致 Node.js 进程崩溃 (从 Node.js 15 开始),在浏览器中会触发 unhandledrejection 事件。
// 自定义错误类型
class NetworkError extends Error {
constructor(message, statusCode) {
super(message);
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
// 区分错误类型进行处理
async function createUser(data) {
try {
validateUserData(data); // 可能抛出 ValidationError
const user = await apiCreateUser(data); // 可能抛出 NetworkError
return user;
} catch (error) {
if (error instanceof ValidationError) {
console.error(`字段 ${error.field} 验证失败: ${error.message}`);
return { error: 'validation', field: error.field };
}
if (error instanceof NetworkError && error.statusCode === 409) {
console.warn('用户已存在');
return { error: 'conflict' };
}
// 无法处理的错误:重新抛出
throw error;
}
}
// 全局未处理拒绝捕获(Node.js)
process.on('unhandledRejection', (reason, promise) => {
console.error('未处理的 Promise 拒绝:', reason);
});
// 浏览器中的全局捕获
window.addEventListener('unhandledrejection', event => {
console.error('未处理的 Promise 拒绝:', event.reason);
event.preventDefault();
});7. 高级模式
Promise.withResolvers()(ES2024)
// 旧写法:手动保存 resolve/reject
let resolve, reject;
const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
// 新写法:Promise.withResolvers()(Node.js 22+)
const { promise, resolve, reject } = Promise.withResolvers();
// 使用场景:在不同作用域中控制 Promise
function createEventPromise(emitter, event) {
const { promise, resolve, reject } = Promise.withResolvers();
emitter.once(event, resolve);
emitter.once('error', reject);
return promise;
}
const connected = createEventPromise(socket, 'connect');
await connected;
console.log('已连接!');Promisification:将回调转为 Promise
const { promisify } = require('util');
const fs = require('fs');
// Node.js 内置 util.promisify
const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);
// 使用 promisified 函数
const content = await readFile('./config.json', 'utf8');
const config = JSON.parse(content);
// 手动 promisify(通用模式)
function promisifyFn(fn) {
return function(...args) {
return new Promise((resolve, reject) => {
fn(...args, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
};
}
// 整个模块 promisify(Node.js fs.promises 已内置)
const fsPromises = fs.promises;
const data = await fsPromises.readFile('./data.txt', 'utf8');8. 真实场景示例
带重试逻辑的 fetch
async function fetchWithRetry(url, options = {}, retries = 3, delay = 1000) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url, options);
if (!response.ok) {
if (response.status >= 500 && attempt < retries) {
console.warn(`尝试 ${attempt}/${retries} 失败,重试中...`);
await new Promise(r => setTimeout(r, delay * attempt));
continue;
}
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
if (attempt === retries) throw error;
await new Promise(r => setTimeout(r, delay * attempt));
}
}
}超时包装器
function withTimeout(promise, ms, message = `操作超时 (${ms}ms)`) {
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error(message)), ms)
);
return Promise.race([promise, timeoutPromise]);
}
// 使用方式
try {
const data = await withTimeout(
fetch('https://slow-api.example.com/data'),
5000,
'API 请求超时(5秒)'
);
console.log(data);
} catch (error) {
if (error.message.includes('超时')) {
return getCachedData();
}
throw error;
}并发控制(限制并发数)
// 限制并发数,避免同时发出过多请求
async function limitedConcurrency(tasks, concurrency = 5) {
const results = [];
const executing = new Set();
for (const task of tasks) {
const promise = task().then(result => {
executing.delete(promise);
return result;
});
executing.add(promise);
results.push(promise);
if (executing.size >= concurrency) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// 使用示例:处理1000个URL,最多同时5个请求
const urls = Array.from({ length: 1000 }, (_, i) => `/api/item/${i}`);
const tasks = urls.map(url => () => fetch(url).then(r => r.json()));
const allResults = await limitedConcurrency(tasks, 5);9. 常见错误及解决方案
错误1:忘记 await
// 错误:没有 await,data 是 Promise 对象而不是值
async function wrong() {
const data = fetchData(); // data = Promise { <pending> }
console.log(data.name); // undefined
if (data) { ... } // 总是 true!
}
// 正确:
async function correct() {
const data = await fetchData();
console.log(data.name);
}错误2:吞掉错误(空的 catch)
// 错误:忽略错误,导致静默失败
async function wrong() {
try {
await doSomething();
} catch (e) {
// 空 catch:错误被吞掉!
}
}
// 正确:至少记录错误
async function correct() {
try {
await doSomething();
} catch (error) {
console.error('操作失败:', error);
throw error;
}
}错误3:嵌套 Promise
// 错误:Promise 嵌套
fetchUser(id).then(user => {
fetchPosts(user.id).then(posts => {
renderPage(user, posts);
});
});
// 正确:async/await
async function loadPage(id) {
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
return renderPage(user, posts);
}10. 回调 vs Promise vs async/await 对比
| 特性 | 回调 | Promise | async/await |
|---|---|---|---|
| 可读性 | 差(嵌套) | 一般(链式) | 优秀(同步风格) |
| 错误处理 | 容易遗漏 | .catch() 链 | try/catch |
| 调试 | 困难 | 一般 | 优秀(堆栈清晰) |
| 并行执行 | 手动管理 | Promise.all | await Promise.all |
| TypeScript 支持 | 一般 | 好 | 最好 |
| 适用场景 | 遗留代码 | 组合操作 | 一般首选 |
常见问题
Promise 的三种状态是什么?
pending(等待)、fulfilled(已完成)、rejected(已拒绝)。一旦从 pending 过渡,状态永不改变。
async/await 和 .then() 有什么区别?
async/await 是语法糖,底层仍是 Promise。async/await 更易读和调试,.then() 在动态组合链时更灵活。
Promise.all 和 Promise.allSettled 的区别?
Promise.all 在任一失败时立即失败;Promise.allSettled 等待全部完成并返回每个的状态,永不失败。
如何给 Promise 添加超时?
使用 Promise.race([yourPromise, timeoutPromise]),timeoutPromise 在指定时间后拒绝。
如何并行执行多个 async 操作?
使用 await Promise.all([op1(), op2(), op3()]),不要连续使用多个独立的 await。