Promise 与 async/await
JavaScript 是单线程语言,异步编程是其核心能力之一。从回调函数到 Promise,再到 async/await,异步代码的写法变得越来越接近同步代码的直观性。
回调地狱与 Promise 的诞生
在 Promise 出现之前,多层嵌套的回调函数让代码难以维护:
// 回调地狱(Callback Hell)
readFile('a.txt', (err, data) => {
if (err) { /* 处理错误 */ return; }
parse(data, (err, result) => {
if (err) { /* 处理错误 */ return; }
writeFile('b.txt', result, (err) => {
if (err) { /* 处理错误 */ return; }
// ...
});
});
});Promise 提供了一种更优雅的方式来组织异步代码。
Promise 基础
三种状态
一个 Promise 始终处于以下三种状态之一:
- pending(待定):初始状态
- fulfilled(已完成):操作成功
- rejected(已拒绝):操作失败
状态一旦改变就不可再次更改:pending → fulfilled 或 pending → rejected。
创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('操作成功!'); // 状态变为 fulfilled
} else {
reject(new Error('操作失败')); // 状态变为 rejected
}
}, 1000);
});消费 Promise
promise
.then(result => {
console.log(result); // "操作成功!"
return result.toUpperCase();
})
.then(upper => {
console.log(upper); // "操作成功!"
})
.catch(error => {
console.error(error.message); // 捕获前面任何阶段的错误
})
.finally(() => {
console.log('无论成败都会执行');
});
then可以接收两个参数:then(onFulfilled, onRejected),但通常将错误处理统一交给catch。
then 的链式传递
function fetchUser(id) {
return new Promise((resolve) => {
setTimeout(() => resolve({ id, name: 'Alice' }), 100);
});
}
function fetchOrders(user) {
return new Promise((resolve) => {
setTimeout(() => resolve([{ id: 1, total: 100 }]), 100);
});
}
// 链式调用,每个 then 接收上一个 then 的返回值
fetchUser(1)
.then(user => {
console.log(user); // { id: 1, name: 'Alice' }
return fetchOrders(user); // 返回新的 Promise
})
.then(orders => {
console.log(orders); // [{ id: 1, total: 100 }]
return orders.length;
})
.then(count => {
console.log(`共 ${count} 笔订单`);
});如果 then 中返回一个 Promise,链条会自动等待它完成。
Promise 组合 API
Promise.all —— 全部完成
等待所有 Promise 全部成功,任一失败则整体失败:
const p1 = fetch('/api/users');
const p2 = fetch('/api/posts');
const p3 = fetch('/api/comments');
Promise.all([p1, p2, p3])
.then(([users, posts, comments]) => {
console.log('全部加载完成');
})
.catch(error => {
console.error('任一请求失败:', error);
});Promise.race —— 竞速
返回最先完成的那个 Promise 的结果:
// 超时控制模式
const fetchWithTimeout = (url, timeout = 5000) => {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
)
]);
};
fetchWithTimeout('/api/data')
.then(response => response.json())
.catch(err => console.error(err.message));Promise.allSettled —— 全部 settled
等待所有 Promise 完成(无论成功或失败),返回每个 Promise 的状态和结果:
const promises = [
Promise.resolve('success'),
Promise.reject('error'),
Promise.resolve('another success')
];
Promise.allSettled(promises)
.then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: 'success' },
// { status: 'rejected', reason: 'error' },
// { status: 'fulfilled', value: 'another success' }
// ]
});Promise.any —— 任一成功(ES2021)
返回最先成功的 Promise,全部失败则返回 AggregateError:
const mirrors = [
fetch('https://mirror-a.com/data'),
fetch('https://mirror-b.com/data'),
fetch('https://mirror-c.com/data')
];
Promise.any(mirrors)
.then(response => response.json())
.catch(err => console.error('所有镜像都失败了:', err.errors));async/await 语法糖
async/await 让异步代码看起来像同步代码,是 Promise 的语法糖。
基本用法
// async 函数总是返回 Promise
async function getUserData() {
const response = await fetch('/api/user');
const user = await response.json();
return user; // 自动包装为 Promise.resolve(user)
}
// 调用
getUserData().then(user => console.log(user));await 会暂停 async 函数的执行,等待 Promise 完成,然后返回其结果。
与 Promise 等价转换
// async/await
async function loadData() {
const a = await fetchA();
const b = await fetchB(a.id);
return b;
}
// 等价的 Promise 写法
function loadData() {
return fetchA().then(a => {
return fetchB(a.id);
}).then(b => {
return b;
});
}并行执行
await 默认是串行的,需要并行时先用 Promise.all:
async function loadDashboard() {
// 串行:慢!
// const user = await fetchUser();
// const posts = await fetchPosts();
// const stats = await fetchStats();
// 并行:快!
const [user, posts, stats] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchStats()
]);
return { user, posts, stats };
}错误处理
try/catch/finally
async function updateProfile(data) {
try {
const response = await fetch('/api/profile', {
method: 'POST',
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('更新失败:', error.message);
// 可以重新抛出或返回默认值
throw error;
} finally {
// 无论成功失败都会执行
console.log('请求结束');
}
}条件性捕获
async function fetchData() {
try {
return await fetch('/api/data');
} catch (err) {
if (err.name === 'AbortError') {
console.log('请求被取消');
return null;
}
throw err; // 其他错误继续向上抛
}
}async/await 与事件循环
理解 await 的执行时机对排查问题至关重要:
async function demo() {
console.log('A');
await Promise.resolve(); // 微任务,当前代码执行完后才恢复
console.log('B');
}
console.log('C');
demo();
console.log('D');
// 输出顺序:C → A → D → B执行过程:
console.log('C')- 进入
demo(),console.log('A') - 遇到
await,demo暂停,将后续代码加入微任务队列 console.log('D')- 当前调用栈清空,执行微任务 →
console.log('B')
常见模式与最佳实践
1. 睡眠函数
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function demo() {
console.log('开始');
await sleep(1000);
console.log('1秒后');
}2. 重试机制
async function retry(fn, maxAttempts = 3, delay = 1000) {
for (let i = 1; i <= maxAttempts; i++) {
try {
return await fn();
} catch (err) {
if (i === maxAttempts) throw err;
await sleep(delay);
}
}
}
const result = await retry(() => fetch('/api/flaky'), 3);3. 串行执行数组
// 串行处理(保持顺序)
async function processInSequence(items, processor) {
const results = [];
for (const item of items) {
results.push(await processor(item));
}
return results;
}
// 对比:并行处理
async function processInParallel(items, processor) {
return Promise.all(items.map(processor));
}4. 顶级 await(模块中)
// module.mjs
const data = await fetch('/api/config').then(r => r.json());
export { data };顶级 await 仅在 ES Module 中可用,模块会等待异步操作完成后再导出。
小结
| API | 用途 | 返回条件 |
|---|---|---|
Promise.all | 全部成功 | 全部 fulfilled |
Promise.race | 竞速 | 第一个 settled |
Promise.allSettled | 等待全部 | 全部 settled |
Promise.any | 任一成功 | 第一个 fulfilled |
async/await 让异步代码更易读,但底层仍是 Promise。理解两者的等价关系和事件循环中的执行顺序,是写出可靠异步代码的基础。
评论
Written by
AI-Writer
Related Articles
手写栈与队列:从基础结构到实战应用
从零实现 Stack、Queue、Deque 与 Priority Queue,深入理解后进先出与先进先出的底层原理,并通过括号匹配、任务调度等实战案例巩固知识。
Read More手写哈希表:哈希函数、冲突解决与 LRU Cache 实战
深入理解哈希表的核心原理,手写 HashMap 与 HashSet,掌握链地址法与开放地址法两种冲突解决策略,并实现经典的 LRU Cache 缓存淘汰算法。
Read More