Day 25: 异步编程Promise
📅 日期: 2026年03月18日
🎯 学习目标: 深入理解异步编程与 Promise,掌握异步操作的最佳实践
⏱️ 预计用时: 2-3 小时
📂 分类: 10-现代JavaScript
🎯 学习目标
通过今天的学习,你将:
- 理解异步编程模型:事件循环、回调队列
- 掌握 Promise 用法:创建、链式调用、错误处理
- 学会 async/await:异步代码的同步写法
- 实践并行处理:Promise.all、race、allSettled
- 应用最佳实践:错误处理、性能优化
💡 核心概念
1. 同步 vs 异步
// 同步代码:阻塞执行
console.log('1');
const data = readFile('data.txt'); // 阻塞,等待文件读取完成
console.log('2');
console.log(data);
// 输出:1 -> 数据 -> 2
// 异步代码:非阻塞执行
console.log('1');
readFile('data.txt', (err, data) => {
console.log(data);
});
console.log('2');
// 输出:1 -> 2 -> 数据
2. 回调函数的问题
// 回调地狱(Callback Hell)
getData(id, (data) => {
getMoreData(data.id, (moreData) => {
getEvenMoreData(moreData.id, (evenMoreData) => {
// 继续嵌套...
});
});
});
3. Promise 基础
// 创建 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作
const success = true;
if (success) {
resolve('操作成功'); // 成功时调用
} else {
reject('操作失败'); // 失败时调用
}
});
// 使用 Promise
promise
.then(result => console.log(result))
.catch(error => console.error(error));
🛠️ API 函数详解
1. Promise 构造函数
const myPromise = new Promise((resolve, reject) => {
// executor 函数:执行异步操作
// resolve:成功时调用
// reject:失败时调用
setTimeout(() => {
const success = Math.random() > 0.5;
if (success) {
resolve('数据加载成功');
} else {
reject(new Error('数据加载失败'));
}
}, 1000);
});
2. Promise 实例方法
// then():处理成功状态
promise.then(value => {
console.log('成功:', value);
return value + 1; // 返回值传递给下一个 then
}).then(newValue => {
console.log('新值:', newValue);
});
// catch():处理失败状态
promise.catch(error => {
console.error('错误:', error.message);
});
// finally():无论成功失败都执行
promise.finally(() => {
console.log('清理资源');
});
3. Promise 静态方法
// Promise.all:所有 Promise 都成功才成功
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(values => {
console.log(values); // [1, 2, 3]
})
.catch(error => {
console.error('至少一个失败:', error);
});
// Promise.race:第一个完成的 Promise 结果
const fast = new Promise(resolve => setTimeout(() => resolve('快'), 100));
const slow = new Promise(resolve => setTimeout(() => resolve('慢'), 200));
Promise.race([fast, slow])
.then(value => console.log(value)); // '快'
// Promise.allSettled:返回所有 Promise 的结果
const p1 = Promise.resolve(1);
const p2 = Promise.reject(2);
Promise.allSettled([p1, p2])
.then(results => {
console.log(results);
// [
// { status: 'fulfilled', value: 1 },
// { status: 'rejected', reason: 2 }
// ]
});
🎮 实战示例
示例 1:封装异步操作
// 封装 fetch 为 Promise
function fetchJSON(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error('HTTP ' + response.status);
}
return response.json();
})
.then(data => resolve(data))
.catch(error => reject(error));
});
}
// 使用
fetchJSON('/api/data')
.then(data => console.log(data))
.catch(error => console.error(error));
示例 2:异步文件操作
// 读取文件的 Promise 版本(Node.js)
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
// 使用
readFilePromise('./data.txt')
.then(data => console.log(data))
.catch(err => console.error('读取失败:', err));
示例 3:顺序执行多个异步操作
// 顺序执行:使用 then 链
function getUser(id) {
return fetch('/api/users/' + id).then(r => r.json());
}
function getPosts(userId) {
return fetch('/api/users/' + userId + '/posts').then(r => r.json());
}
function getComments(postId) {
return fetch('/api/posts/' + postId + '/comments').then(r => r.json());
}
// 依次执行
getUser(1)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => console.log(comments))
.catch(error => console.error(error));
⚠️ 重要注意事项
1. 总是返回或传递 Promise
// 正确:总是返回 Promise
function getData() {
return fetch('/api/data').then(r => r.json());
}
// 错误:不返回 Promise
function getData() {
fetch('/api/data').then(r => r.json());
// 调用者无法获取结果
}
2. 总是处理错误
// 正确:总是处理错误
promise
.then(data => processData(data))
.catch(error => handleError(error));
// 错误:不处理错误
promise.then(data => processData(data));
// 错误会变成未捕获的 Promise rejection
3. 避免嵌套 Promise
// 正确:扁平化 then 链
promise1
.then(result1 => {
return promise2(result1);
})
.then(result2 => {
return promise3(result2);
});
// 错误:嵌套 Promise
promise1.then(result1 => {
promise2(result1).then(result2 => {
promise3(result2).then(result3 => {
// 又是回调地狱
});
});
});
4. Promise 状态不可逆
const promise = new Promise((resolve) => {
resolve('第一次');
resolve('第二次'); // 无效,Promise 状态不会改变
reject('失败'); // 无效
});
promise.then(console.log); // 只输出 '第一次'
🎓 实战应用场景
场景 1: API 请求
// 并行请求多个 API
async function getDashboardData() {
const [users, posts, comments] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/comments').then(r => r.json())
]);
return { users, posts, comments };
}
场景 2: 超时控制
// Promise.race 实现超时
function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
);
return Promise.race([fetchPromise, timeoutPromise]);
}
场景 3: 图片预加载
// Promise.all 预加载多张图片
function preloadImages(urls) {
const promises = urls.map(url => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = () => reject(new Error('加载失败: ' + url));
img.src = url;
});
});
return Promise.all(promises);
}
✍️ 练习任务
基础练习(必做)
-
Promise 创建:
- [ ] 创建一个延迟 2 秒后返回的 Promise
- [ ] 创建一个随机成功/失败的 Promise
- [ ] 使用 then/catch 处理结果
-
链式调用:
- [ ] 实现至少 3 个 then 的链式调用
- [ ] 在 then 中返回新的 Promise
- [ ] 使用 finally 清理资源
进阶练习(推荐)
-
并行操作:
- 使用 Promise.all 并行请求 3 个 API
- 使用 Promise.race 实现请求超时
- 使用 Promise.allSettled 处理部分失败
-
错误处理:
- 实现统一的错误处理函数
- 区分不同类型的错误
- 提供友好的错误提示
-
工具函数:
// 延迟函数 function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } // 重试函数 async function retry(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (i === maxRetries - 1) throw error; await delay(1000 * (i + 1)); } } }
实战挑战(选做)
-
实现 Promise 方法:
- Promise.map:并行处理数组
- Promise.filter:并行过滤数组
- Promise.reduce:并行归约数组
-
取消机制:
- 实现 AbortController
- 在超时或取消时清理资源
-
进度追踪:
- 报告多个 Promise 的完成进度
- 实现进度条显示
💡 常见问题 FAQ
Q1: 如何调试 Promise 链?
A:
// 在 then 中添加日志
promise
.then(data => {
console.log('Step 1:', data);
return nextStep(data);
})
.then(data => {
console.log('Step 2:', data);
return finalStep(data);
});
Q2: 如何处理多个独立的错误?
A:
// 每个操作独立错误处理
const promises = tasks.map(task =>
task().catch(error => ({ error, task }))
);
const results = await Promise.all(promises);
Q3: Promise 和 async/await 有什么区别?
A:
- async/await 是 Promise 的语法糖
- async/await 让异步代码看起来像同步代码
- 底层仍然使用 Promise
- 可以混用 Promise 和 async/await
Q4: 如何避免未捕获的 Promise rejection?
A:
// 全局未捕获 rejection 处理(Node.js)
process.on('unhandledRejection', (reason, promise) => {
console.error('未捕获的 Promise rejection:', reason);
});
// 或在浏览器中
window.addEventListener('unhandledrejection', (event) => {
console.error('未捕获的 Promise rejection:', event.reason);
event.preventDefault(); // 阻止默认的控制台错误
});
📚 拓展阅读
官方文档
推荐资源
相关工具
🎉 总结
今天我们深入学习了 异步编程与 Promise,涵盖了:
✅ 异步编程模型:理解事件循环和非阻塞 I/O
✅ Promise 用法:创建、链式调用、错误处理
✅ 并行处理:Promise.all、race、allSettled
✅ 最佳实践:错误处理、性能优化
✅ 实战应用:API 请求、超时控制、图片预加载
Promise 是现代 JavaScript 异步编程的基础,掌握它对于理解 async/await 和更复杂的异步模式至关重要。
下一节课预告: Day 26 将学习 模块化开发与构建工具,学习如何组织大型项目!
💪 加油!坚持就是胜利!