JavaScript异步编程:FE-Interview中的Promise/async考点
你是否还在为异步编程中的回调地狱烦恼?是否在面试中被Promise状态转换问题难住?本文将通过FE-Interview项目中的经典题目,带你一文掌握Promise/async/await的核心考点,解决90%的异步编程面试问题。
Promise核心特性解析
Promise(承诺)是JavaScript中处理异步操作的标准方案,它解决了传统回调函数嵌套过深的问题。在FE-Interview/summarry/javascript.md中,编号427题详细介绍了Promise的实现原理。
三种状态与状态转换
Promise有三种不可逆转的状态:
- Pending(进行中):初始状态
- Fulfilled(已成功):操作完成
- Rejected(已失败):操作出错
状态转换路径只有两种:
- Pending → Fulfilled
- Pending → Rejected
核心方法使用示例
// 创建Promise实例
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.5) {
resolve('操作成功'); // 状态变为Fulfilled
} else {
reject(new Error('操作失败')); // 状态变为Rejected
}
}, 1000);
});
// 处理结果
promise
.then(result => console.log(result)) // 成功回调
.catch(error => console.error(error)) // 失败回调
.finally(() => console.log('操作完成')); // 无论成功失败都会执行
手写Promise实现
面试中常考手写Promise实现,FE-Interview/summarry/javascript.md的427题要求实现一个完整的Promise。核心要点包括:
- 状态管理(pending/fulfilled/rejected)
- 回调函数存储与执行
- then方法链式调用
- 错误捕获机制
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = null;
this.reason = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
Promise并发控制方法
在实际开发中,我们经常需要控制并发请求数量。FE-Interview/summarry/javascript.md的730题要求实现一个带并发限制的请求函数。
Promise.all与Promise.race
Promise提供了两个常用的并发控制方法:
- Promise.all:等待所有Promise完成,返回结果数组
- Promise.race:只要有一个Promise完成,就返回其结果
// 实现Promise.all
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
return reject(new TypeError('参数必须是数组'));
}
const results = [];
let completed = 0;
const length = promises.length;
if (length === 0) {
return resolve(results);
}
promises.forEach((promise, index) => {
Promise.resolve(promise).then(result => {
results[index] = result;
completed++;
if (completed === length) {
resolve(results);
}
}, reason => {
reject(reason);
});
});
});
};
实现带并发限制的请求函数
// 控制最大并发数的请求函数
function requestWithLimit(urls, max) {
return new Promise((resolve) => {
if (urls.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 当前请求索引
let count = 0; // 已完成请求数量
// 发送请求
async function request() {
if (index >= urls.length) return;
const i = index;
const url = urls[index];
index++;
try {
const response = await fetch(url);
results[i] = await response.json();
} catch (error) {
results[i] = error;
} finally {
count++;
// 所有请求完成
if (count === urls.length) {
resolve(results);
}
// 继续发送请求
request();
}
}
// 启动max个并发请求
const tasks = Math.min(max, urls.length);
for (let i = 0; i < tasks; i++) {
request();
}
});
}
async/await语法糖详解
async/await是ES2017引入的语法糖,基于Promise实现,让异步代码看起来像同步代码。FE-Interview/summarry/javascript.md的109题对比了Promise和async/await的使用场景。
基本用法与错误处理
// 使用async/await重构Promise代码
async function fetchData() {
try {
// 等待Promise完成
const response = await fetch('https://api.example.com/data');
const data = await response.json();
// 同步写法,异步执行
console.log(data);
return data;
} catch (error) {
// 集中错误处理
console.error('请求失败:', error);
throw error; // 可以继续向上抛出错误
}
}
// 调用async函数
fetchData()
.then(data => console.log('处理结果:', data))
.catch(error => console.error('捕获错误:', error));
并发请求优化
使用async/await时,可以通过Promise.all实现并发请求,提高性能:
async function fetchMultipleData() {
try {
// 同时发起多个请求,而不是顺序执行
const [userData, productData, orderData] = await Promise.all([
fetch('/api/user').then(res => res.json()),
fetch('/api/products').then(res => res.json()),
fetch('/api/orders').then(res => res.json())
]);
return { userData, productData, orderData };
} catch (error) {
console.error('获取数据失败:', error);
}
}
常见面试题解析
题目1:Promise状态转换与链式调用
问题:Promise有几种状态?通过catch捕获到reject之后,在catch后面还能继续执行then方法吗?
解答:Promise有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。状态一旦改变就不会再变。catch之后可以继续执行then,因为catch返回的仍然是一个Promise对象。
Promise.reject(new Error('错误'))
.catch(error => {
console.error('捕获错误:', error);
return '从错误中恢复'; // 返回正常值,使后续then执行成功回调
})
.then(result => {
console.log('继续执行:', result); // 会执行,输出"继续执行: 从错误中恢复"
return Promise.reject(new Error('新错误'));
})
.then(
() => console.log('不会执行'),
error => console.error('捕获新错误:', error) // 会执行
);
题目2:async/await错误处理最佳实践
问题:如何在async/await中捕获多个异步操作的错误?
解答:可以使用try/catch捕获所有错误,或为每个await表达式单独添加错误处理:
async function multipleOperations() {
// 方法1: 统一捕获所有错误
try {
const result1 = await asyncOperation1();
const result2 = await asyncOperation2();
} catch (error) {
console.error('捕获错误:', error);
}
// 方法2: 单独处理每个操作的错误
const result3 = await asyncOperation3().catch(error => {
console.error('操作3失败:', error);
return null; // 返回默认值
});
const result4 = await asyncOperation4().catch(error => {
console.error('操作4失败:', error);
return null;
});
return { result3, result4 };
}
题目3:Promise执行顺序
问题:分析以下代码的输出顺序:
console.log('start');
setTimeout(() => {
console.log('setTimeout');
}, 0);
new Promise((resolve) => {
console.log('promise executor');
resolve();
}).then(() => {
console.log('promise then');
});
console.log('end');
解答:输出顺序为:start → promise executor → end → promise then → setTimeout。
原因是:
- 同步代码优先执行
- Promise构造函数是同步执行的
- then回调属于微任务(Microtask),在同步代码执行完后立即执行
- setTimeout回调属于宏任务(Macrotask),在微任务之后执行
异步编程最佳实践
1. 避免嵌套结构
即使使用Promise,也应避免不必要的嵌套:
// 不好的写法
getUser(userId)
.then(user => {
getPosts(user.id)
.then(posts => {
getComments(posts[0].id)
.then(comments => {
console.log(comments);
});
});
});
// 好的写法
getUser(userId)
.then(user => getPosts(user.id))
.then(posts => getComments(posts[0].id))
.then(comments => console.log(comments))
.catch(error => console.error(error));
// 最好的写法 (async/await)
async function getCommentsForFirstPost(userId) {
try {
const user = await getUser(userId);
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
return comments;
} catch (error) {
console.error(error);
throw error;
}
}
2. 错误处理策略
为所有异步操作添加错误处理:
// 全局未捕获错误处理
window.addEventListener('unhandledrejection', event => {
console.error('未处理的Promise错误:', event.reason);
// 可以设置event.preventDefault()来阻止浏览器默认处理
});
// 局部错误处理
async function criticalOperation() {
try {
// 关键操作
await riskyOperation();
} catch (error) {
// 记录错误日志
logErrorToService(error);
// 展示友好提示
showUserMessage('操作失败,请稍后重试');
// 可以选择重新尝试或回滚操作
if (shouldRetry(error)) {
return retryOperation();
}
}
}
总结
异步编程是JavaScript面试的必考点,掌握Promise和async/await不仅能应对面试,更能写出优雅高效的异步代码。通过FE-Interview项目中的JavaScript题目汇总,可以系统学习更多异步编程相关题目。
关键知识点回顾:
- Promise的三种状态及状态转换规则
- then方法的链式调用和值传递
- async/await语法糖的使用和错误处理
- 微任务与宏任务的执行顺序
- 并发控制方法(Promise.all, Promise.race等)
掌握这些知识点,你就能从容应对各类异步编程面试题,写出更健壮的异步代码。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



