
JavaScript Promise 与 Async/Await:完整可视化指南
异步编程是 JavaScript 中最难掌握的概念之一。本指南涵盖从回调到 Promise 再到 async/await 的所有内容——配有可视化解释和真实世界模式。
问题:回调地狱
// 旧方式——"回调金字塔"
getUser(userId, function(err, user) {
if (err) return handleError(err);
getOrders(user.id, function(err, orders) {
if (err) return handleError(err);
getProducts(orders[0].id, function(err, products) {
if (err) return handleError(err);
// 现在你已经嵌套了 3 层...
displayResults(user, orders, products);
});
});
});
Promise 和 async/await 解决了这个问题。
理解 Promise
Promise 是一个表示异步操作最终完成(或失败)的对象。
// 创建一个 promise
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('数据加载完成!'); // 成功
} else {
reject(new Error('加载失败')); // 失败
}
}, 1000);
});
三种状态:
- Pending(待定) — 初始状态,操作进行中
- Fulfilled(已兑现) — 操作成功完成
- Rejected(已拒绝) — 操作失败
Promise 链式调用
// 扁平、可读的链式调用
fetchUser(userId)
.then(user => fetchOrders(user.id)) // 每个 .then 接收前一个结果
.then(orders => fetchProducts(orders))
.then(products => displayProducts(products))
.catch(error => console.error('出错了:', error))
.finally(() => setLoading(false)); // 始终执行
关键规则:.then() 总是返回一个新的 Promise,因此你可以无限链式调用。
Async/Await——更简洁的语法
// 相同逻辑,但读起来像同步代码
async function loadUserData(userId) {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
const products = await fetchProducts(orders);
displayProducts(products);
} catch (error) {
console.error('出错了:', error);
} finally {
setLoading(false);
}
}
async/await 的规则:
async函数总是返回一个 Promiseawait只能在async函数内部使用await会暂停执行直到 Promise 解决
并行执行——最重要的模式
❌ 顺序执行(慢)——逐个等待:
// 如果每个请求需要 1 秒,总共需要 3 秒
async function loadDashboard() {
const users = await fetchUsers(); // 1s
const orders = await fetchOrders(); // 1s
const stats = await fetchStats(); // 1s
return { users, orders, stats }; // 总计:3s
}
✅ 并行执行(快)——同时运行:
// 只需要 1 秒(所有请求同时运行)
async function loadDashboard() {
const [users, orders, stats] = await Promise.all([
fetchUsers(),
fetchOrders(),
fetchStats(),
]);
return { users, orders, stats }; // 总计:1s
}
何时使用哪种:
- 当请求相互独立时,使用
Promise.all - 当请求 B 依赖于请求 A 的结果时,使用顺序
await
Promise 方法详解
Promise.all——全部或全不
// 当所有 Promise 都解决时解决;任何一个拒绝则拒绝
const [user, profile, settings] = await Promise.all([
fetchUser(id),
fetchProfile(id),
fetchSettings(id),
]);
Promise.allSettled——获取所有结果
// 当所有 Promise 完成时解决(无论成功或失败)
const results = await Promise.allSettled([
fetchUser(id),
fetchProfile(id),
fetchSettings(id),
]);
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('成功:', result.value);
} else {
console.log('失败:', result.reason);
}
});
Promise.race——先到先得
// 以第一个解决的 Promise 的结果解决或拒绝
// 常用于超时处理
const result = await Promise.race([
fetchData(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('超时')), 5000)
)
]);
Promise.any——第一个成功
// 以第一个兑现的 Promise 的结果解决
// 仅当所有 Promise 都拒绝时才拒绝(抛出 AggregateError)
const result = await Promise.any([
fetchFromServer1(),
fetchFromServer2(),
fetchFromServer3(),
]);
// 返回最先成功响应的服务器结果
错误处理模式
Try/Catch 模式
async function fetchUserSafe(id) {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP 错误:${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求已取消');
} else if (error.name === 'TypeError') {
console.log('网络错误——你在线吗?');
} else {
console.error('意外错误:', error);
}
return null; // 或者重新抛出,取决于你的需求
}
}
Result 模式(Go 风格错误处理)
// 返回 [data, error] 元组——调用处无需 try/catch
async function safeAsync(promise) {
try {
const data = await promise;
return [data, null];
} catch (error) {
return [null, error];
}
}
// 使用
const [user, error] = await safeAsync(fetchUser(id));
if (error) {
console.error('失败:', error);
return;
}
// 此处 user 保证已定义
console.log(user.name);
常见错误
错误 1:循环中未使用 await
// ❌ 错误——所有请求同时触发,无法正确捕获错误
async function processItems(items) {
items.forEach(async (item) => {
await processItem(item); // forEach 不会等待异步回调!
});
}
// ✅ 正确——顺序处理
async function processItemsSequential(items) {
for (const item of items) {
await processItem(item);
}
}
// ✅ 正确——并行处理
async function processItemsParallel(items) {
await Promise.all(items.map(item => processItem(item)));
}
错误 2:遗漏 await
// ❌ 错误——console.log 得到的是 Promise 对象,而不是数据
async function getUser() {
const user = fetchUser(); // 遗漏了 await!
console.log(user); // Promise { <pending> }
}
// ✅ 正确
async function getUser() {
const user = await fetchUser();
console.log(user); // { id: 1, name: 'John' }
}
错误 3:未处理的 Promise 拒绝
// ❌ 危险——未处理的拒绝可能导致 Node.js 崩溃
async function riskyOperation() {
await doSomething();
}
riskyOperation(); // 没有任何 .catch()!
// ✅ 始终处理拒绝
riskyOperation().catch(console.error);
// 或者在模块中使用顶层 await
try {
await riskyOperation();
} catch (e) {
console.error(e);
}
错误 4:创建不必要的 Promise
// ❌ 冗余——将 async 函数包装在新 Promise 中
function fetchData() {
return new Promise(async (resolve, reject) => {
try {
const data = await fetch('/api/data');
resolve(data);
} catch (error) {
reject(error);
}
});
}
// ✅ async 函数已经返回 Promise
async function fetchData() {
const data = await fetch('/api/data');
return data; // 自动包装在 Promise 中
}
真实世界模式:带重试的数据获取
async function fetchWithRetry(url, options = {}, retries = 3) {
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(url, options);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
if (attempt === retries) throw error;
// 指数退避:等待 1s, 2s, 4s...
const delay = Math.pow(2, attempt - 1) * 1000;
console.log(`第 ${attempt} 次尝试失败。${delay}ms 后重试...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
// 使用
const data = await fetchWithRetry('https://api.example.com/data');
总结
| 模式 | 使用场景 |
|---|---|
顺序 await |
每一步依赖前一步的结果 |
Promise.all |
独立操作,需要所有结果 |
Promise.allSettled |
需要所有结果,部分可能失败 |
Promise.race |
超时模式,第一个结果胜出 |
Promise.any |
多个来源,需要第一个成功结果 |
→ 使用 JSON Viewer 测试 JSON API 响应,并在 JWT Parser 解码 JWT 令牌。