一、Promise 是什么?
Promise 是 ES6 引入的异步编程的解决方案,用于解决传统的"回调地狱"问题。你可以把它想象成一个"承诺"------我现在给你一个承诺,将来要么成功(resolve),要么失败(reject)。
1.1 为什么需要 Promise?
先看看"回调地狱"的样子:
javascript
// 回调地狱示例
getUser(1, function(user) {
getOrders(user.id, function(orders) {
getOrderDetails(orders[0].id, function(details) {
getProductInfo(details.productId, function(product) {
console.log(product);
// 如果还有更多异步操作... 代码会向右无限延伸
});
});
});
});
二、Promise 基础
2.1 创建 Promise
javascript
// 创建一个 Promise
const promise = new Promise((resolve, reject) => {
// 异步操作,比如读取文件、请求API等
const success = true; // 假设操作成功
if (success) {
resolve('操作成功!'); // 承诺兑现
} else {
reject('操作失败!'); // 承诺拒绝
}
});
// 使用 Promise
promise
.then(result => {
console.log('成功:', result);
})
.catch(error => {
console.log('失败:', error);
});
2.2 Promise 的三种状态
-
pending(等待中):初始状态
-
fulfilled(已成功):操作成功完成
-
rejected(已失败):操作失败
javascript
const promise = new Promise((resolve, reject) => {
console.log('Promise 创建,状态:pending');
setTimeout(() => {
resolve('成功数据');
console.log('状态变为:fulfilled');
}, 1000);
});
promise.then(data => {
console.log('接收到数据:', data);
});
三、Promise 的核心方法
3.1 .then() - 处理成功
javascript
const promise = new Promise(resolve => {
setTimeout(() => resolve('数据1'), 1000);
});
promise.then(
// 第一个参数:成功回调
data => {
console.log('第一次处理:', data);
return data + ' -> 处理后的数据';
},
// 第二个参数:失败回调(不推荐用这个,推荐用.catch)
error => {
console.error('错误:', error);
}
).then(newData => {
console.log('第二次处理:', newData);
// 输出:第二次处理: 数据1 -> 处理后的数据
});
3.2 .catch() - 处理失败
javascript
const promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('网络错误')), 1000);
});
promise
.then(data => {
console.log('成功:', data);
})
.catch(error => {
console.error('捕获到错误:', error.message);
// 可以返回新值,继续链式调用
return '默认值';
})
.then(data => {
console.log('继续执行:', data);
// 输出:继续执行: 默认值
});
3.3 .finally() - 无论成功失败都会执行
javascript
function loadData() {
return new Promise((resolve, reject) => {
const success = Math.random() > 0.5;
setTimeout(() => {
success ? resolve('数据加载成功') : reject('加载失败');
}, 1000);
});
}
loadData()
.then(data => console.log(data))
.catch(error => console.error(error))
.finally(() => {
console.log('无论成功失败,我都会执行');
// 通常用于清理工作,如隐藏loading图标
});
四、Promise 链式调用
这是 Promise 最强大的特性!
javascript
// 模拟异步操作
function delay(ms, value) {
return new Promise(resolve => {
setTimeout(() => resolve(value), ms);
});
}
// 链式调用示例
delay(1000, '第一步')
.then(result => {
console.log(result);
return delay(500, '第二步');
})
.then(result => {
console.log(result);
return delay(300, '第三步');
})
.then(result => {
console.log(result);
return '所有步骤完成!';
})
.then(finalResult => {
console.log(finalResult);
});
// 等价于(如果不用Promise):
// setTimeout(() => {
// console.log('第一步');
// setTimeout(() => {
// console.log('第二步');
// setTimeout(() => {
// console.log('第三步');
// console.log('所有步骤完成!');
// }, 300);
// }, 500);
// }, 1000);
五、Promise 静态方法
5.1 Promise.resolve() - 创建已成功的 Promise
javascript
// 创建一个立即成功的 Promise
Promise.resolve('立即成功')
.then(data => console.log(data));
// 输出:立即成功
// 等价于
new Promise(resolve => resolve('立即成功'));
// 传入 Promise 对象
const originalPromise = new Promise(resolve => resolve('原始'));
Promise.resolve(originalPromise)
.then(data => console.log(data)); // 输出:原始
5.2 Promise.reject() - 创建已失败的 Promise
javascript
// 创建一个立即失败的 Promise
Promise.reject(new Error('立即失败'))
.catch(error => console.error(error.message));
// 输出:立即失败
5.3 Promise.all() - 等待所有 Promise 完成
javascript
const p1 = Promise.resolve('任务1');
const p2 = new Promise(resolve =>
setTimeout(() => resolve('任务2'), 1000)
);
const p3 = fetch('https://api.example.com/data'); // 假设返回Promise
// 所有Promise都成功才成功
Promise.all([p1, p2, p3])
.then(results => {
console.log('所有任务完成:', results);
// results 是一个数组,包含每个Promise的结果
// 顺序与传入的数组顺序一致
})
.catch(error => {
// 任意一个失败,整个Promise.all就失败
console.error('有任务失败:', error);
});
// 实际例子:同时请求多个API
const userId = 1;
Promise.all([
fetch(`/api/users/${userId}`),
fetch(`/api/users/${userId}/orders`),
fetch(`/api/users/${userId}/profile`)
])
.then(([user, orders, profile]) => {
// 处理所有数据
})
.catch(error => console.error('请求失败', error));
5.4 Promise.race() - 竞速,第一个完成(无论成功失败)的 Promise
javascript
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), 5000)
);
const apiRequest = fetch('https://api.example.com/data');
// 谁先完成就用谁的结果
Promise.race([apiRequest, timeout])
.then(data => {
console.log('API请求成功');
})
.catch(error => {
console.error('请求超时或失败');
});
5.5 Promise.allSettled() - 等待所有 Promise 完成(无论成功失败)
javascript
const promises = [
Promise.resolve('成功1'),
Promise.reject('失败1'),
Promise.resolve('成功2')
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index}: 成功`, result.value);
} else {
console.log(`Promise ${index}: 失败`, result.reason);
}
});
});
// 输出:
// Promise 0: 成功 成功1
// Promise 1: 失败 失败1
// Promise 2: 成功 成功2
5.6 Promise.any() - 第一个成功的 Promise(ES2021)
javascript
const promises = [
Promise.reject('错误1'),
new Promise(resolve => setTimeout(() => resolve('成功1'), 1000)),
new Promise(resolve => setTimeout(() => resolve('成功2'), 500))
];
Promise.any(promises)
.then(firstSuccess => {
console.log('第一个成功的:', firstSuccess);
// 输出:第一个成功的: 成功2
})
.catch(error => {
// 只有所有Promise都失败才进入catch
console.error('全部失败');
});
六、Promise 错误处理
6.1 错误冒泡
javascript
// 错误会一直向后传递,直到被catch捕获
Promise.resolve()
.then(() => {
throw new Error('第一步出错');
})
.then(() => {
console.log('这里不会执行');
})
.catch(error => {
console.error('捕获到错误:', error.message);
// 可以重新抛出
// throw error;
});
// 多个catch的情况
new Promise((resolve, reject) => {
reject('初始错误');
})
.catch(error => {
console.log('第一个catch:', error);
throw '新错误';
})
.catch(error => {
console.log('第二个catch:', error);
return '恢复正常';
})
.then(data => {
console.log('恢复正常后:', data);
});
6.2 异步错误处理
javascript
// 错误示例:无法捕获异步错误
try {
new Promise((resolve, reject) => {
setTimeout(() => {
reject('异步错误');
}, 1000);
});
} catch (error) {
// 这里捕获不到!
console.error('捕获不到:', error);
}
// 正确做法
new Promise((resolve, reject) => {
setTimeout(() => {
try {
// 可能出错的代码
throw new Error('异步错误');
} catch (error) {
reject(error);
}
}, 1000);
})
.catch(error => {
console.error('正确捕获:', error.message);
});
七、Promise 实际应用场景
7.1 封装回调函数为 Promise
javascript
// 将setTimeout封装为Promise
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 使用
delay(1000)
.then(() => console.log('1秒后执行'));
// 封装fs.readFile(Node.js)
const fs = require('fs');
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, 'utf8', (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}
// 使用
readFilePromise('./file.txt')
.then(content => console.log(content))
.catch(error => console.error(error));
7.2 顺序执行异步操作
javascript
// 用户注册流程
function registerUser(userData) {
return validateUser(userData)
.then(() => checkEmailExists(userData.email))
.then(() => createUser(userData))
.then(user => sendWelcomeEmail(user))
.then(() => logRegistration(userData))
.catch(error => {
console.error('注册失败:', error);
throw error;
});
}
// 模拟各个步骤
function validateUser(data) {
return new Promise((resolve, reject) => {
if (!data.email) reject('邮箱不能为空');
else resolve();
});
}
function checkEmailExists(email) {
return delay(300).then(() => {
// 模拟数据库查询
const exists = Math.random() > 0.5;
if (exists) throw '邮箱已存在';
});
}
function createUser(data) {
return delay(500).then(() => ({
id: Date.now(),
...data
}));
}
7.3 控制并发数量
javascript
// 限制并发数量的Promise处理
async function processWithConcurrency(tasks, concurrency = 3) {
const results = [];
const executing = [];
for (const task of tasks) {
const p = Promise.resolve().then(() => task());
results.push(p);
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= concurrency) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// 使用
const urls = ['url1', 'url2', 'url3', 'url4', 'url5'];
const tasks = urls.map(url => () => fetch(url));
processWithConcurrency(tasks, 2)
.then(responses => console.log('所有请求完成'))
.catch(error => console.error(error));
八、Promise 常见陷阱和最佳实践
8.1 常见陷阱
javascript
// 陷阱1:忘记return
Promise.resolve(1)
.then(value => {
value + 1; // 忘记return!
})
.then(value => {
console.log(value); // undefined
});
// 陷阱2:在then中抛出错误但不处理
Promise.resolve()
.then(() => {
throw new Error('未处理的错误');
}); // 没有catch,错误会静默失败
// 陷阱3:嵌套Promise
// 错误写法
promise.then(result => {
anotherPromise.then(anotherResult => {
// 回调地狱又回来了!
});
});
// 正确写法
promise
.then(result => anotherPromise)
.then(anotherResult => {
// 扁平化的链式调用
});
8.2 最佳实践
javascript
// 1. 总是使用catch
someAsyncFunction()
.then(handleSuccess)
.catch(handleError); // 不要省略!
// 2. 使用async/await(ES8)让代码更清晰
async function getUserData(userId) {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
const profile = await fetchProfile(user.id);
return { user, orders, profile };
} catch (error) {
console.error('获取用户数据失败:', error);
throw error;
}
}
// 3. 给Promise命名,便于调试
const userPromise = fetchUser(1);
const orderPromise = fetchOrders(1);
Promise.all([userPromise, orderPromise])
.then(([user, orders]) => {
// 清晰的命名
});
// 4. 避免在Promise构造函数中写太多逻辑
// 不好
const promise = new Promise((resolve, reject) => {
// 几十行业务逻辑...
});
// 好
function doComplexTask() {
// 复杂逻辑放在外部函数
return prepareData()
.then(processData)
.then(validateData);
}
九、Promise 面试题解析
9.1 基础题
javascript
// 问题:输出顺序是什么?
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve()
.then(() => console.log('3'))
.then(() => console.log('4'));
console.log('5');
// 答案:1 5 3 4 2
// 解析:同步代码 > 微任务(Promise) > 宏任务(setTimeout)
9.2 进阶题
javascript
// 问题:输出顺序是什么?
Promise.resolve()
.then(() => {
console.log('then1');
Promise.resolve()
.then(() => console.log('then1-1'))
.then(() => console.log('then1-2'));
})
.then(() => console.log('then2'));
// 答案:then1 → then1-1 → then2 → then1-2
// 解析:Promise链的微妙执行顺序
十、总结
Promise 的核心优势:
-
链式调用:解决回调地狱
-
错误统一处理 :通过
.catch()统一处理错误 -
状态可追踪:明确知道异步操作的状态
-
组合能力强 :通过
Promise.all()、Promise.race()等组合多个异步操作
现代异步编程的发展:
javascript
// 1. 回调函数时代
readFile('a.txt', (err, data) => {
if (err) handleError(err);
else process(data);
});
// 2. Promise时代
readFilePromise('a.txt')
.then(process)
.catch(handleError);
// 3. Async/Await时代(基于Promise)
async function processFile() {
try {
const data = await readFilePromise('a.txt');
return process(data);
} catch (error) {
handleError(error);
}
}
记住:Promise 不是万能的,但它为异步编程提供了一个坚实的基础。理解 Promise 的工作原理,会让你更好地使用 async/await 等更高级的语法糖。