JavaScript面试题之Promise
前端异步编程利器:Promise 全方位详解
一、为什么需要 Promise?
在传统 JavaScript 异步编程中,我们主要使用回调函数处理异步操作。但当多个异步操作存在依赖关系时,就会出现著名的 “回调地狱”(Callback Hell):
getUser(userId, function(user) {getOrders(user.id, function(orders) {getProducts(orders[0].id, function(products) {renderPage(products, function() {// 更多嵌套...});});});
});
这种代码存在三大痛点:
- 可读性差:层层嵌套的金字塔结构
- 错误处理困难:需要在每个回调中单独处理错误
- 流程控制复杂:难以实现并行、顺序等复杂操作
Promise 的出现正是为了解决这些问题,它为我们提供了:
- 链式调用:扁平化的代码结构
- 统一错误处理:通过 catch 方法集中处理
- 状态可追踪:明确的生命周期管理
二、Promise 核心概念
2.1 三种状态
每个 Promise 实例都处于以下三种状态之一:
- pending(进行中)→ 初始状态
- fulfilled(已成功)→ 通过 resolve() 转换
- rejected(已失败)→ 通过 reject() 转换
状态转换示意图:
resolve(value)
pending ---------------> fulfilled|| reject(reason)---------------> rejected
状态特性:
- 不可逆:一旦状态变化就不能逆转
- 不可变:状态确定后不能再修改
2.2 创建 Promise
const promise = new Promise((resolve, reject) => {// 异步操作setTimeout(() => {const success = Math.random() > 0.5;success ? resolve('成功数据') : reject('错误原因');}, 1000);
});
关键点解析:
- Executor 函数:立即执行的同步函数
- resolve/reject:改变 Promise 状态的函数
- 只能调用一次:多次调用会被忽略
三、使用 Promise
3.1 基础使用方法
promise.then(value => { /* 成功处理 */ },reason => { /* 失败处理 */ }).catch(error => { /* 错误处理 */ }).finally(() => { /* 最终执行 */ });
方法解析表:
方法 | 作用 | 返回值 |
---|---|---|
.then() | 处理成功/失败结果 | 新 Promise |
.catch() | 捕获链中所有错误 | 新 Promise |
.finally() | 无论成功失败都执行(ES2018) | 新 Promise |
3.2 链式调用原理
doFirstTask().then(result => doSecondTask(result)).then(newResult => doThirdTask(newResult)).catch(error => handleError(error));
链式调用特点:
- 每个 then 返回新 Promise
- 值会通过链传递
- 错误会冒泡直到被捕获
3.3 错误处理机制
对比传统方式:
// 回调风格
function getUser(id, callback, errorCallback) {// ...
}// Promise 风格
getUser(id).then(user => { /* ... */ }).catch(error => { /* 统一处理所有错误 */ });
错误处理最佳实践:
- 总是使用 catch 处理错误
- 避免在 then 中写两个参数
- 使用 throw 主动触发错误
fetchData().then(data => {if (!data.valid) {throw new Error('Invalid data');}return process(data);}).catch(error => {console.error('处理失败:', error);});
四、高级应用技巧
4.1 组合多个 Promise
方法 | 作用 | 特点 |
---|---|---|
Promise.all() | 所有成功时返回结果数组 | 快速失败策略 |
Promise.race() | 第一个 settled 的结果 | 可用于超时控制 |
Promise.allSettled() | 等待所有完成(无论成功失败) | 获取每个结果状态 |
Promise.any() | 第一个成功的结果(ES2021) | 忽略所有失败,直到第一个成功 |
示例:并行加载多个资源
const [user, orders] = await Promise.all([fetch('/api/user'),fetch('/api/orders')
]);
4.2 常见问题解决方案
问题1:顺序执行异步操作
function sequenceTasks(tasks) {return tasks.reduce((promiseChain, currentTask) => {return promiseChain.then(chainResults => currentTask().then(currentResult => [...chainResults, currentResult]));}, Promise.resolve([]));
}
问题2:超时控制
function withTimeout(promise, timeout) {return Promise.race([promise,new Promise((_, reject) => setTimeout(() => reject(new Error('超时')), timeout))]);
}
五、最佳实践与常见陷阱
5.1 必须遵守的规则
-
永远返回 Promise(避免断链)
// 错误示例 .then(user => {saveUser(user); // 没有 return })// 正确写法 .then(user => {return saveUser(user); })
-
始终处理错误(避免未捕获的拒绝)
-
合理使用 async/await(ES2017+)
5.2 性能优化建议
- 避免不必要的链式调用
- 尽早处理错误
- 合理使用 Promise 缓存
- 注意内存泄漏(保留未完成的 Promise)
六、从 Promise 到 async/await
虽然 async/await 让异步代码更像同步写法,但其本质仍然是 Promise:
async function fetchData() {try {const response = await fetch('/api/data');const data = await response.json();return process(data);} catch (error) {handleError(error);}
}
转换规则:
- async 函数总是返回 Promise
- await 后面跟 Promise 对象
- try/catch 可以捕获同步和异步错误
七、总结
Promise 的核心优势:
✅ 扁平化的链式调用
✅ 集中式的错误处理
✅ 更强大的流程控制
✅ 良好的浏览器支持(IE11+)
学习路线建议:
- 掌握基础用法 → 2. 理解事件循环机制 → 3. 学习高级模式 → 4. 过渡到 async/await
Promise 作为现代 JavaScript 异步编程的基石,配合 async/await 语法糖,使复杂异步逻辑变得清晰可维护。掌握其核心原理与最佳实践,将显著提升代码质量与开发效率。通过合理使用 Promise,开发者可以写出更简洁、更健壮的异步代码,显著提升前端应用的可维护性和可读性。