你是怎么理解ES6中Promise的?使用场景?
问题解析
Promise 是 ES6 引入的异步编程解决方案,用于解决回调地狱问题。这个问题考察对 Promise 核心概念、状态流转、方法使用以及实际应用场景的理解。
核心概念
1. Promise 是什么
- 异步编程的一种解决方案,比传统的回调函数和事件更合理
- 代表一个异步操作的最终完成(或失败)及其结果值
2. 三种状态
pending(进行中):初始状态fulfilled(已成功):操作成功完成rejected(已失败):操作失败- 状态一旦改变就不可再变(
pending->fulfilled或pending->rejected)
3. 基本用法
- 通过
new Promise(executor)创建 executor接收resolve和reject两个参数- 使用
.then()、.catch()、.finally()处理结果
4. 静态方法
Promise.all():所有 Promise 成功才成功Promise.race():返回最快完成的 PromisePromise.allSettled():等待所有 Promise 完成(无论成功失败)Promise.resolve()/Promise.reject():快速创建已决 Promise
详细解答
1. Promise 基本用法
const promise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('Operation completed successfully');
} else {
reject(new Error('Operation failed'));
}
}, 1000);
});
// 处理结果
promise
.then(value => {
console.log('Success:', value);
})
.catch(error => {
console.error('Error:', error.message);
})
.finally(() => {
console.log('Cleanup code here');
});
2. 状态流转
const promise = new Promise((resolve, reject) => {
console.log(promise); // 无法访问,此时 promise 还未赋值
// pending 状态
setTimeout(() => {
resolve('success'); // 变为 fulfilled 状态
// 或者
// reject(new Error('failure')); // 变为 rejected 状态
// 状态一旦确定就不可改变
resolve('again'); // 被忽略
reject(new Error('again')); // 被忽略
}, 1000);
});
// 状态变化是单向的:pending -> fulfilled 或 pending -> rejected
3. 实例方法
then()
const promise = Promise.resolve(1);
promise
.then(value => {
console.log(value); // 1
return value * 2;
})
.then(value => {
console.log(value); // 2
return value * 2;
})
.then(value => {
console.log(value); // 4
});
// then 返回新的 Promise,支持链式调用
// 如果返回普通值,会被包装成 resolved Promise
// 如果返回 Promise,会等待其完成
// 如果抛出错误,会被包装成 rejected Promise
catch()
Promise.reject(new Error('fail'))
.then(value => {
// 不会执行
})
.catch(error => {
console.error(error.message); // 'fail'
return 'recovered';
})
.then(value => {
console.log(value); // 'recovered' - 可以继续链式调用
});
// catch 等价于 then(null, onRejected)
promise.then(null, error => {
console.error(error);
});
finally()
Promise.resolve('success')
.then(value => {
console.log(value);
})
.finally(() => {
// 无论成功失败都会执行
console.log('Cleanup');
});
// finally 不接收参数,它的返回值会被忽略(除非抛出错误)
4. 静态方法
Promise.all()
// 所有 Promise 都成功时返回成功的数组
const promises = [
fetch('/api/user'),
fetch('/api/posts'),
fetch('/api/comments')
];
Promise.all(promises)
.then(([user, posts, comments]) => {
// 所有请求都成功
})
.catch(error => {
// 任一请求失败
});
// 注意:如果传入空数组,立即返回 resolved Promise
Promise.all([]).then(values => {
console.log(values); // []
});
Promise.race()
// 返回最快完成的 Promise(无论成功失败)
const timeout = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Timeout')), 5000);
});
const fetchData = fetch('/api/data');
Promise.race([fetchData, timeout])
.then(response => {
// fetch 先完成
})
.catch(error => {
// timeout 先完成或 fetch 失败
});
Promise.allSettled()
// 等待所有 Promise 完成,无论成功失败
const promises = [
Promise.resolve('success'),
Promise.reject(new Error('failure')),
Promise.resolve('another success')
];
Promise.allSettled(promises)
.then(results => {
results.forEach(result => {
if (result.status === 'fulfilled') {
console.log('Success:', result.value);
} else {
console.log('Failure:', result.reason);
}
});
});
// 结果格式:
// [
// { status: 'fulfilled', value: 'success' },
// { status: 'rejected', reason: Error },
// { status: 'fulfilled', value: 'another success' }
// ]
Promise.any() (ES2021)
// 返回第一个成功的 Promise,如果全部失败返回 AggregateError
const promises = [
fetch('/api/primary'),
fetch('/api/backup1'),
fetch('/api/backup2')
];
Promise.any(promises)
.then(response => {
// 第一个成功的响应
})
.catch(error => {
// 所有 Promise 都失败
console.log(error.errors); // 所有错误组成的数组
});
Promise.resolve() / reject()
// 快速创建 resolved Promise
Promise.resolve('value');
// 等价于
new Promise(resolve => resolve('value'));
// 将 thenable 转为 Promise
const thenable = {
then(resolve, reject) {
resolve(42);
}
};
Promise.resolve(thenable).then(value => {
console.log(value); // 42
});
// 快速创建 rejected Promise
Promise.reject(new Error('error'));
// 等价于
new Promise((_, reject) => reject(new Error('error')));
深入理解
1. Promise 解决了什么问题
回调地狱(Callback Hell)
// 回调地狱示例
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
console.log(d);
});
});
});
});
// Promise 解决方案
getData()
.then(a => getMoreData(a))
.then(b => getMoreData(b))
.then(c => getMoreData(c))
.then(d => console.log(d))
.catch(error => console.error(error));
// async/await(基于 Promise)
async function fetchData() {
try {
const a = await getData();
const b = await getMoreData(a);
const c = await getMoreData(b);
const d = await getMoreData(c);
console.log(d);
} catch (error) {
console.error(error);
}
}
错误处理
// 回调的错误处理复杂
async1(function(error, result1) {
if (error) {
handleError(error);
return;
}
async2(result1, function(error, result2) {
if (error) {
handleError(error);
return;
}
// ...
});
});
// Promise 统一错误处理
async1()
.then(result1 => async2(result1))
.then(result2 => async3(result2))
.catch(error => handleError(error)); // 捕获链中任何错误
2. Promise 的链式调用机制
// then 返回新的 Promise,支持链式调用
const promise = Promise.resolve(1);
const promise2 = promise.then(value => value * 2);
// promise2 是新的 Promise,不是原来的 promise
// 返回值的类型决定新 Promise 的状态
Promise.resolve(1)
.then(v => v * 2) // 返回普通值 -> resolved
.then(v => Promise.resolve(v * 2)) // 返回 Promise -> 等待其完成
.then(v => { throw new Error(); }) // 抛出错误 -> rejected
.catch(e => 'recovered') // 捕获错误,返回普通值 -> resolved
.then(v => console.log(v)); // 'recovered'
3. Promise 的执行时机
console.log('1');
const promise = new Promise((resolve) => {
console.log('2');
resolve('3');
});
promise.then(value => console.log(value));
console.log('4');
// 输出顺序:1, 2, 4, 3
// Promise 构造函数是同步执行的
// then/catch/finally 的回调是异步执行的(微任务)
4. Promise 与微任务
console.log('Start');
setTimeout(() => console.log('Timeout'), 0);
Promise.resolve()
.then(() => console.log('Promise 1'))
.then(() => console.log('Promise 2'));
console.log('End');
// 输出顺序:
// Start
// End
// Promise 1
// Promise 2
// Timeout
// 解释:Promise 回调是微任务,在宏任务(setTimeout)之前执行
5. Promise 的局限性
// 1. 一旦创建无法取消
const promise = new Promise((resolve) => {
setTimeout(() => resolve('done'), 5000);
});
// 无法取消这个 5 秒的等待
// 2. 不设置回调函数,内部错误不会反应到外部
const promise2 = new Promise((resolve, reject) => {
reject(new Error('error'));
});
// 如果没有 catch,错误会被静默处理(可能报错)
// 3. pending 状态时无法得知进度
// Promise 只有 pending/fulfilled/rejected 三种状态
// 无法获取进行中的进度信息
// 4. 错误捕获的坑
Promise.resolve()
.then(() => {
throw new Error('error in then');
})
.catch(error => {
console.log('Caught:', error.message);
throw new Error('error in catch');
})
.catch(error => {
console.log('Caught again:', error.message);
});
使用场景
1. 图片加载
function loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error(`Failed to load ${src}`));
img.src = src;
});
}
// 使用
loadImage('https://example.com/image.jpg')
.then(img => {
document.body.appendChild(img);
})
.catch(error => {
console.error('Image load failed:', error);
// 显示占位图
});
// 加载多张图片
const imageUrls = ['1.jpg', '2.jpg', '3.jpg'];
Promise.all(imageUrls.map(loadImage))
.then(images => {
images.forEach(img => document.body.appendChild(img));
});
2. 链式操作
// 用户登录流程
function login(credentials) {
return fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
}).then(res => res.json());
}
function getUserProfile(userId) {
return fetch(`/api/users/${userId}`).then(res => res.json());
}
function getUserPermissions(userId) {
return fetch(`/api/users/${userId}/permissions`).then(res => res.json());
}
// 链式调用
login({ username, password })
.then(user => {
saveToken(user.token);
return getUserProfile(user.id);
})
.then(profile => {
updateUI(profile);
return getUserPermissions(profile.id);
})
.then(permissions => {
configureAccess(permissions);
})
.catch(error => {
showError(error.message);
});
3. 请求合并
// 并行请求多个资源
function loadDashboardData() {
const userPromise = fetch('/api/user').then(r => r.json());
const statsPromise = fetch('/api/stats').then(r => r.json());
const notificationsPromise = fetch('/api/notifications').then(r => r.json());
return Promise.all([
userPromise,
statsPromise,
notificationsPromise
]).then(([user, stats, notifications]) => ({
user,
stats,
notifications
}));
}
// 使用
loadDashboardData()
.then(data => renderDashboard(data))
.catch(error => showError('Failed to load dashboard'));
4. 超时处理
function fetchWithTimeout(url, options = {}, timeout = 5000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('Request timeout')), timeout);
})
]);
}
// 使用
fetchWithTimeout('/api/data', {}, 3000)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.message === 'Request timeout') {
console.log('Request took too long');
} else {
console.log('Request failed:', error);
}
});
5. 重试机制
function fetchWithRetry(url, options = {}, maxRetries = 3) {
return fetch(url, options).catch(error => {
if (maxRetries <= 0) {
throw error;
}
console.log(`Retrying... ${maxRetries} attempts left`);
return fetchWithRetry(url, options, maxRetries - 1);
});
}
// 带延迟的重试
function fetchWithRetryAndDelay(url, options = {}, maxRetries = 3, delay = 1000) {
return fetch(url, options).catch(error => {
if (maxRetries <= 0) {
throw error;
}
return new Promise(resolve => setTimeout(resolve, delay))
.then(() => fetchWithRetryAndDelay(url, options, maxRetries - 1, delay));
});
}
6. 顺序执行异步任务
// 顺序执行一组异步任务
const urls = ['/api/1', '/api/2', '/api/3'];
// 方式1:使用 reduce
urls.reduce((promise, url) => {
return promise.then(results => {
return fetch(url)
.then(r => r.json())
.then(data => [...results, data]);
});
}, Promise.resolve([]))
.then(allResults => console.log(allResults));
// 方式2:使用 async/await
async function fetchSequentially(urls) {
const results = [];
for (const url of urls) {
const response = await fetch(url);
const data = await response.json();
results.push(data);
}
return results;
}
7. 缓存 Promise
class PromiseCache {
constructor() {
this.cache = new Map();
}
get(key, factory) {
if (this.cache.has(key)) {
return this.cache.get(key);
}
const promise = factory().finally(() => {
// 可选:请求完成后从缓存中移除
// this.cache.delete(key);
});
this.cache.set(key, promise);
return promise;
}
clear() {
this.cache.clear();
}
}
// 使用
const cache = new PromiseCache();
function getUser(userId) {
return cache.get(`user-${userId}`, () => {
return fetch(`/api/users/${userId}`).then(r => r.json());
});
}
// 多次调用返回同一个 Promise
getUser(1).then(user => console.log(user));
getUser(1).then(user => console.log(user)); // 使用缓存
最佳实践
1. 始终返回 Promise
// 推荐:函数始终返回 Promise
function getData() {
if (cache.hasData()) {
return Promise.resolve(cache.getData());
}
return fetch('/api/data').then(r => r.json());
}
// 不推荐:有时返回 Promise,有时返回值
function getDataBad() {
if (cache.hasData()) {
return cache.getData(); // 返回值
}
return fetch('/api/data'); // 返回 Promise
}
2. 错误处理
// 推荐:在链式末尾统一处理错误
fetchData()
.then(processData)
.then(saveData)
.catch(handleError);
// 推荐:在可能出错的地方分别处理
fetchData()
.then(data => {
try {
return processData(data);
} catch (error) {
return defaultData;
}
})
.then(saveData)
.catch(handleError);
// 不推荐:忽略错误
fetchData().then(data => console.log(data));
// 如果 fetchData 失败,错误会被静默处理
3. 避免 Promise 构造函数反模式
// 反模式:包装已经是 Promise 的值
function bad() {
return new Promise((resolve, reject) => {
fetch('/api/data')
.then(response => resolve(response))
.catch(error => reject(error));
});
}
// 正确:直接返回 Promise
function good() {
return fetch('/api/data');
}
// 反模式:同步代码使用 Promise
function bad2() {
return new Promise((resolve) => {
const result = someSyncOperation();
resolve(result);
});
}
// 正确:使用 Promise.resolve()
function good2() {
return Promise.resolve(someSyncOperation());
}
4. 使用 async/await
// Promise 链式调用
fetchUser()
.then(user => fetchOrders(user.id))
.then(orders => processOrders(orders))
.catch(error => handleError(error));
// async/await 更清晰
async function loadUserOrders() {
try {
const user = await fetchUser();
const orders = await fetchOrders(user.id);
return processOrders(orders);
} catch (error) {
return handleError(error);
}
}
5. 并发控制
// 限制并发数量
async function fetchWithConcurrencyLimit(urls, limit = 5) {
const results = [];
const executing = [];
for (const [index, url] of urls.entries()) {
const promise = fetch(url)
.then(r => r.json())
.then(data => ({ index, data }));
results.push(promise);
if (urls.length >= limit) {
executing.push(promise);
if (executing.length >= limit) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p === promise), 1);
}
}
}
return Promise.all(results);
}
面试要点
-
Promise 状态:
- 三种状态:
pending、fulfilled、rejected - 状态一旦改变不可再变
- 理解状态流转的时机
- 三种状态:
-
then 的链式调用:
then返回新的 Promise- 返回值类型决定新 Promise 状态
- 错误会沿着链向下传递直到被捕获
-
静态方法的区别:
Promise.all:所有成功才成功,一个失败就失败Promise.race:返回最快完成的(无论成功失败)Promise.allSettled:等待所有完成,返回状态数组Promise.any:返回第一个成功的(ES2021)
-
错误处理:
catch可以捕获链中任何位置的错误- 没有
catch的错误可能被静默处理 finally不接收参数,返回值被忽略
-
Promise 与微任务:
- Promise 回调是微任务
- 微任务优先级高于宏任务(setTimeout/setInterval)
- 理解事件循环中 Promise 的执行时机
-
Promise 的局限性:
- 无法取消
- 无法获取进度
- 不设置回调时错误可能被静默处理
-
实际应用场景:
- 图片/资源加载
- 请求合并与并发控制
- 超时处理
- 重试机制
- 链式异步操作
-
async/await 与 Promise:
async/await是基于 Promise 的语法糖async函数返回 Promiseawait会暂停执行,等待 Promise 完成- 使用
try/catch处理错误