Promise是JavaScript中处理异步操作的现代解决方案,它解决了传统回调函数的诸多问题。
Promise的基本概念
什么是Promise?
Promise是一个代表异步操作最终完成或失败的对象。它有三种状态:
- Pending(待定):初始状态,既没有被兑现,也没有被拒绝
- Fulfilled(已兑现):操作成功完成
- Rejected(已拒绝):操作失败
// Promise的基本语法
const promise = new Promise((resolve, reject) => {
// 异步操作
if (/* 操作成功 */) {
resolve(value); // 成功时调用
} else {
reject(error); // 失败时调用
}
});Promise解决的问题
1. 回调地狱(Callback Hell)
// 传统回调方式 - 回调地狱
function getUserData(userId, callback) {
setTimeout(() => {
console.log('获取用户数据');
callback(null, { id: userId, name: 'John' });
}, 1000);
}
function getOrderData(userId, callback) {
setTimeout(() => {
console.log('获取订单数据');
callback(null, [{ id: 1, product: 'iPhone' }]);
}, 1000);
}
function getProductDetails(productId, callback) {
setTimeout(() => {
console.log('获取产品详情');
callback(null, { id: productId, price: 999 });
}, 1000);
}
// 嵌套回调 - 难以维护
getUserData(123, (err, user) => {
if (err) {
console.error(err);
return;
}
getOrderData(user.id, (err, orders) => {
if (err) {
console.error(err);
return;
}
getProductDetails(orders[0].id, (err, product) => {
if (err) {
console.error(err);
return;
}
console.log('最终结果:', { user, orders, product });
});
});
});// 使用Promise - 链式调用,更清晰
function getUserData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('获取用户数据');
resolve({ id: userId, name: 'John' });
}, 1000);
});
}
function getOrderData(userId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('获取订单数据');
resolve([{ id: 1, product: 'iPhone' }]);
}, 1000);
});
}
function getProductDetails(productId) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('获取产品详情');
resolve({ id: productId, price: 999 });
}, 1000);
});
}
// 链式调用 - 扁平化结构
getUserData(123)
.then(user => {
console.log('用户:', user);
return getOrderData(user.id);
})
.then(orders => {
console.log('订单:', orders);
return getProductDetails(orders[0].id);
})
.then(product => {
console.log('产品:', product);
})
.catch(error => {
console.error('错误:', error);
});Promise的基本用法
1. 创建Promise
// 基本创建方式
const simplePromise = new Promise((resolve, reject) => {
const success = Math.random() > 0.5;
setTimeout(() => {
if (success) {
resolve('操作成功!');
} else {
reject(new Error('操作失败!'));
}
}, 1000);
});
// 使用Promise
simplePromise
.then(result => {
console.log('成功:', result);
})
.catch(error => {
console.log('失败:', error.message);
});2. Promise.resolve() 和 Promise.reject()
// 创建已解决的Promise
const resolvedPromise = Promise.resolve('立即成功');
const rejectedPromise = Promise.reject(new Error('立即失败'));
// 包装非Promise值
Promise.resolve(42)
.then(value => console.log(value)); // 42
// 包装thenable对象
const thenable = {
then(resolve, reject) {
resolve('thenable值');
}
};
Promise.resolve(thenable)
.then(value => console.log(value)); // 'thenable值'3. 链式调用
Promise.resolve(1)
.then(value => {
console.log('第一步:', value); // 1
return value * 2;
})
.then(value => {
console.log('第二步:', value); // 2
return value * 3;
})
.then(value => {
console.log('第三步:', value); // 6
return Promise.resolve(value * 4);
})
.then(value => {
console.log('第四步:', value); // 24
})
.catch(error => {
console.error('错误:', error);
});Promise的静态方法
1. Promise.all() - 并行执行,全部成功
const promise1 = Promise.resolve(3);
const promise2 = new Promise(resolve => setTimeout(() => resolve('foo'), 1000));
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // [3, 'foo', 42]
})
.catch(error => {
console.error('有一个失败了:', error);
});
// 实际应用:并行获取多个API数据
async function fetchAllData() {
try {
const [users, products, orders] = await Promise.all([
fetch('/api/users').then(res => res.json()),
fetch('/api/products').then(res => res.json()),
fetch('/api/orders').then(res => res.json())
]);
return { users, products, orders };
} catch (error) {
console.error('获取数据失败:', error);
}
}2. Promise.allSettled() - 等待所有Promise完成
const promises = [
Promise.resolve('成功1'),
Promise.reject(new Error('失败1')),
Promise.resolve('成功2'),
Promise.reject(new Error('失败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.message);
}
});
});
// 输出:
// Promise 0 成功: 成功1
// Promise 1 失败: 失败1
// Promise 2 成功: 成功2
// Promise 3 失败: 失败23. Promise.race() - 竞态,返回最先完成的
const promise1 = new Promise(resolve => setTimeout(() => resolve('慢的'), 2000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('快的'), 1000));
Promise.race([promise1, promise2])
.then(value => {
console.log(value); // '快的'
});
// 实际应用:请求超时控制
function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
);
return Promise.race([fetchPromise, timeoutPromise]);
}
fetchWithTimeout('/api/data', 3000)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error.message));4. Promise.any() - 返回第一个成功的
const promises = [
Promise.reject(new Error('错误1')),
Promise.reject(new Error('错误2')),
Promise.resolve('成功'),
Promise.resolve('另一个成功')
];
Promise.any(promises)
.then(value => {
console.log('第一个成功的:', value); // '成功'
})
.catch(error => {
console.log('所有都失败了:', error);
});错误处理
1. catch方法
Promise.resolve()
.then(() => {
throw new Error('出错了!');
})
.then(() => {
console.log('这里不会执行');
})
.catch(error => {
console.error('捕获错误:', error.message);
});2. finally方法
function fetchData() {
const loadingElement = document.querySelector('#loading');
loadingElement.style.display = 'block';
return fetch('/api/data')
.then(response => response.json())
.then(data => {
console.log('数据:', data);
return data;
})
.catch(error => {
console.error('错误:', error);
throw error;
})
.finally(() => {
// 无论成功失败都会执行
loadingElement.style.display = 'none';
});
}3. 错误传播
Promise.resolve()
.then(() => {
throw new Error('第一个错误');
})
.then(() => {
console.log('不会执行');
})
.catch(error => {
console.log('捕获:', error.message);
throw new Error('第二个错误');
})
.then(() => {
console.log('也不会执行');
})
.catch(error => {
console.log('最终捕获:', error.message);
});实际应用场景
1. 网络请求封装
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
request(url, options = {}) {
return fetch(this.baseURL + url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.json();
});
}
get(url) {
return this.request(url);
}
post(url, data) {
return this.request(url, {
method: 'POST',
body: JSON.stringify(data)
});
}
}
// 使用
const api = new ApiClient('https://api.example.com');
api.get('/users/123')
.then(user => console.log(user))
.catch(error => console.error('获取用户失败:', error));2. 图片预加载
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`图片加载失败: ${src}`));
img.src = src;
});
}
function preloadImages(urls) {
const promises = urls.map(url => loadImage(url));
return Promise.allSettled(promises)
.then(results => {
const loaded = results
.filter(result => result.status === 'fulfilled')
.map(result => result.value);
const failed = results
.filter(result => result.status === 'rejected')
.map(result => result.reason);
return { loaded, failed };
});
}
// 使用
const imageUrls = [
'image1.jpg',
'image2.jpg',
'image3.jpg'
];
preloadImages(imageUrls)
.then(({ loaded, failed }) => {
console.log(`成功加载 ${loaded.length} 张图片`);
console.log(`失败 ${failed.length} 张图片`);
});3. 缓存机制
class CacheManager {
constructor() {
this.cache = new Map();
this.pendingPromises = new Map();
}
async get(key, fetchFunction) {
// 如果缓存中有数据,直接返回
if (this.cache.has(key)) {
return Promise.resolve(this.cache.get(key));
}
// 如果正在请求中,返回相同的Promise
if (this.pendingPromises.has(key)) {
return this.pendingPromises.get(key);
}
// 创建新的请求Promise
const promise = fetchFunction()
.then(data => {
this.cache.set(key, data);
this.pendingPromises.delete(key);
return data;
})
.catch(error => {
this.pendingPromises.delete(key);
throw error;
});
this.pendingPromises.set(key, promise);
return promise;
}
clear() {
this.cache.clear();
this.pendingPromises.clear();
}
}
// 使用
const cache = new CacheManager();
function fetchUser(id) {
return cache.get(`user-${id}`, () =>
fetch(`/api/users/${id}`).then(res => res.json())
);
}
// 多次调用只会发送一次请求
Promise.all([
fetchUser(123),
fetchUser(123),
fetchUser(123)
]).then(users => {
console.log('所有用户数据:', users);
});Promise与async/await
传统Promise写法
function processData() {
return fetchUser(123)
.then(user => {
return fetchOrders(user.id);
})
.then(orders => {
return Promise.all(
orders.map(order => fetchOrderDetails(order.id))
);
})
.then(orderDetails => {
return {
user,
orders: orderDetails
};
})
.catch(error => {
console.error('处理数据失败:', error);
throw error;
});
}async/await写法
async function processData() {
try {
const user = await fetchUser(123);
const orders = await fetchOrders(user.id);
const orderDetails = await Promise.all(
orders.map(order => fetchOrderDetails(order.id))
);
return {
user,
orders: orderDetails
};
} catch (error) {
console.error('处理数据失败:', error);
throw error;
}
}性能考虑
1. 避免不必要的Promise包装
// 不好 - 不必要的Promise包装
function badExample(value) {
return new Promise(resolve => {
resolve(value * 2);
});
}
// 好 - 直接返回值或使用Promise.resolve
function goodExample(value) {
return Promise.resolve(value * 2);
}
// 更好 - 如果是同步操作,直接返回值
function bestExample(value) {
return value * 2;
}2. 并行 vs 串行
// 串行执行 - 慢
async function serialExample() {
const user = await fetchUser(123); // 1秒
const orders = await fetchOrders(456); // 1秒
const products = await fetchProducts(789); // 1秒
// 总时间: 3秒
return { user, orders, products };
}
// 并行执行 - 快
async function parallelExample() {
const [user, orders, products] = await Promise.all([
fetchUser(123), // 1秒
fetchOrders(456), // 1秒
fetchProducts(789) // 1秒
]);
// 总时间: 1秒
return { user, orders, products };
}总结
Promise的核心价值:
- 解决回调地狱 - 提供链式调用语法
- 统一异步处理 - 标准化异步操作接口
- 更好的错误处理 - 集中化错误处理机制
- 组合能力 - 提供并行、竞态等组合方法
- 与现代语法兼容 - 完美支持async/await
Promise是现代JavaScript异步编程的基础,理解其原理和用法对于编写高质量的异步代码至关重要。