前端人必看!你是不是也被异步操作逼疯过😭
回调地狱嵌套一层又一层,代码乱得像“蜘蛛网”;并发请求太多导致接口拥堵,页面卡顿崩溃;异步请求超时、无法取消,报错无从排查;不知道怎么顺序/并行执行异步任务,越写越乱……
其实这些前端异步痛点,Promise早就给出了完美解决方案!作为JS异步编程的核心,Promise不仅能替代繁琐的回调函数,还能优雅处理并发、超时、重试等复杂场景,是前端工程师必备的核心技能,也是面试高频考点。
今天这篇文章,结合实战场景,把23种Promise高效用法逐一拆解,每一种都配「核心作用+完整可复制代码+通俗解读+实战场景」,从基础到进阶,小白能看懂,老手能查漏补缺,收藏这一篇,再也不用东拼西凑找资料,异步开发效率直接翻倍!
核心提醒:Promise的核心是“异步操作的状态管理”,三种状态(pending/fulfilled/rejected)一旦改变就无法逆转,掌握它的核心用法,能解决80%的前端异步问题,剩下20%靠这23种实战技巧搞定!
一、基础核心用法(3种)—— 入门必备,筑牢基础
这3种是Promise最基础、最常用的用法,是后续高级用法的前提,新手务必先掌握,熟练运用就能告别基础异步烦恼。
1. 使用async/await简化Promise(最常用)
核心作用:用同步代码的写法,实现异步操作,彻底告别回调地狱,代码逻辑更清晰、更易维护,是日常开发中最推荐的异步写法。
通俗解读:async关键字标记函数为异步函数,await关键字“等待”异步操作完成,只有await后面的异步操作结束,才会执行下一行代码,避免嵌套。
// 基础用法:处理单个异步请求
async function asyncFunction() {
try {
// 等待异步操作完成(比如接口请求)
const result = await fetch('/api/data');
const data = await result.json();
// 异步操作成功后,执行后续逻辑
console.log('接口请求成功:', data);
return data;
} catch (error) {
// 捕获异步操作中的所有错误
console.error('接口请求失败:', error);
}
}
// 实战用法:结合业务场景(获取用户信息并渲染)
async function getUserInfo() {
try {
const response = await fetch('/api/user/info');
const user = await response.json();
// 渲染用户信息到页面
document.querySelector('.user-name').textContent = user.name;
document.querySelector('.user-age').textContent = user.age;
} catch (err) {
// 错误处理(比如提示用户获取失败)
alert('获取用户信息失败,请重试!');
}
}
// 调用异步函数
getUserInfo();
2. 使用Promises替代回调函数
核心作用:将传统的回调函数(回调地狱的根源),转化为Promise链式调用,代码更优雅,错误处理更统一。
通俗解读:很多老项目或原生API仍使用回调函数(比如setTimeout、fs.readFile),通过封装,将其转化为Promise,就能享受链式调用的便捷。
// 核心封装:将回调函数转为Promise
const callbackToPromise = (fn, ...args) => {
return new Promise((resolve, reject) => {
// 执行原回调函数,在回调中触发Promise的resolve/reject
fn(...args, (error, result) => {
if (error) {
// 回调报错,触发reject
reject(error);
} else {
// 回调成功,触发resolve
resolve(result);
}
});
});
};
// 实战用法:封装setTimeout(回调转Promise)
const delay = callbackToPromise(setTimeout, 1000);
// 链式调用,替代回调嵌套
delay.then(() => {
console.log('1秒后执行');
return callbackToPromise(setTimeout, 2000);
}).then(() => {
console.log('再等2秒执行');
}).catch(err => {
console.error('出错了:', err);
});
3. 使用Promise实现一个延时函数
核心作用:异步延时操作,常用于防抖、节流、模拟接口请求延迟等场景,比原生setTimeout更灵活,可结合await使用。
// 基础封装:异步延时函数
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
// 实战用法1:结合await使用(同步写法实现延时)
async function delayDemo() {
console.log('开始执行');
await delay(1500); // 延时1.5秒
console.log('1.5秒后执行');
await delay(2000); // 再延时2秒
console.log('总共延时3.5秒后执行');
}
// 实战用法2:模拟接口请求延迟
async function mockFetch() {
console.log('正在请求接口...');
// 模拟接口延迟1秒
await delay(1000);
return { code: 200, data: '模拟接口返回数据', msg: 'success' };
}
mockFetch().then(res => console.log(res)).catch(err => console.error(err));
二、并发控制用法(4种)—— 解决多异步拥堵,提升性能
日常开发中,经常需要同时执行多个异步请求(比如批量获取数据),但并发过多会导致接口拥堵、页面卡顿,这4种用法能优雅控制并发,兼顾效率和性能。
4. 映射并发Promises并处理结果数组(Promise.all)
核心作用:并行执行多个Promise,等待所有Promise都成功(fulfilled)后,返回所有结果的数组;只要有一个失败(rejected),就立即触发catch,适合“所有异步操作都必须成功”的场景。
// 基础用法:并发请求多个接口
const fetchUrls = urls => {
// 映射所有url为fetch Promise
const fetchPromises = urls.map(url =>
fetch(url).then(response => response.json())
);
// 等待所有Promise执行完成
return Promise.all(fetchPromises);
};
// 实战用法:批量获取用户列表、商品列表、分类列表
const urls = [
'/api/users',
'/api/goods',
'/api/categories'
];
fetchUrls(urls).then(results => {
const [users, goods, categories] = results;
console.log('用户列表:', users);
console.log('商品列表:', goods);
console.log('分类列表:', categories);
// 所有数据获取完成后,渲染页面
}).catch(error => {
console.error('某个接口请求失败:', error);
});
5. 并发控制(限制并发数量)
核心作用:Promise.all会同时执行所有Promise,当并发数量过多(比如几十上百个请求),会给服务器造成压力,通过此方法可限制同时执行的Promise数量,避免拥堵。
// 核心封装:并发控制函数
const concurrentPromises = (promises, limit) => {
return new Promise((resolve, reject) => {
let i = 0; // 当前执行到的Promise索引
let result = []; // 存储所有Promise的结果
// 执行单个Promise的函数
const executor = () => {
// 所有Promise都执行完成,返回结果
if (i >= promises.length) {
return resolve(result);
}
// 取出当前要执行的Promise
const promise = promises[i++];
// 执行Promise并处理结果
Promise.resolve(promise)
.then(value => {
result.push(value);
// 继续执行下一个Promise(递归调用)
if (i < promises.length) {
executor();
} else {
// 最后一个Promise执行完成,返回结果
resolve(result);
}
})
.catch(reject); // 任意一个Promise失败,直接触发reject
};
// 初始化,同时执行limit个Promise
for (let j = 0; j < limit && j < promises.length; j++) {
executor();
}
});
};
// 实战用法:限制最多同时执行3个请求
const allPromises = [
fetch('/api/data1').then(res => res.json()),
fetch('/api/data2').then(res => res.json()),
fetch('/api/data3').then(res => res.json()),
fetch('/api/data4').then(res => res.json()),
fetch('/api/data5').then(res => res.json())
];
// 限制并发数量为3
concurrentPromises(allPromises, 3).then(results => {
console.log('所有请求完成,结果:', results);
}).catch(err => {
console.error('请求失败:', err);
});
6. 使用Promise.allSettled处理多个异步操作
核心作用:与Promise.all相反,无论每个Promise是成功还是失败,都会等待所有Promise执行完成,返回每个Promise的状态和结果,适合“不需要所有异步都成功”的场景(比如批量统计接口状态)。
// 基础用法:统计多个接口的执行状态
const promises = [
fetch('/api/endpoint1'),
fetch('/api/endpoint2'),
fetch('/api/endpoint3') // 假设这个接口会失败
];
Promise.allSettled(promises).then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
// 成功的Promise,处理结果
console.log(`接口${index + 1}请求成功:`, result.value);
} else {
// 失败的Promise,处理错误
console.error(`接口${index + 1}请求失败:`, result.reason);
}
});
});
// 实战用法:批量上传文件,统计成功/失败数量
async function batchUpload(files) {
const uploadPromises = files.map(file =>
fetch('/api/upload', {
method: 'POST',
body: file
}).then(res => res.json())
);
const results = await Promise.allSettled(uploadPromises);
const successCount = results.filter(r => r.status === 'fulfilled').length;
const failCount = results.filter(r => r.status === 'rejected').length;
console.log(`上传完成:成功${successCount}个,失败${failCount}个`);
}
7. 处理多个Promises的最快响应(Promise.race)
核心作用:并行执行多个Promise,只关心“最快完成”的那个Promise的结果(无论成功还是失败),适合“取最快响应”的场景(比如多节点接口容错)。
// 基础用法:获取多个接口中最快的响应
const promises = [
fetch('/api/node1').then(res => res.json()), // 节点1,响应时间约500ms
fetch('/api/node2').then(res => res.json()), // 节点2,响应时间约300ms
fetch('/api/node3').then(res => res.json()) // 节点3,响应时间约800ms
];
Promise.race(promises)
.then(value => {
console.log('最快响应的接口数据:', value); // 会输出节点2的数据
// 使用最快的响应数据渲染页面,提升用户体验
})
.catch(reason => {
console.error('最早失败的接口:', reason); // 若最快的接口失败,直接触发
});
// 实战用法:多CDN资源加载,取最快的那个
const loadResource = (urls) => {
const loadPromises = urls.map(url =>
new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(url);
img.src = url;
})
);
return Promise.race(loadPromises);
};
// 加载3个CDN的图片,取最快加载完成的
const imgUrls = [
'https://cdn1.example.com/img.jpg',
'https://cdn2.example.com/img.jpg',
'https://cdn3.example.com/img.jpg'
];
loadResource(imgUrls).then(fastUrl => {
console.log('最快加载的图片CDN:', fastUrl);
document.querySelector('.img').src = fastUrl;
});
三、异常与状态处理用法(5种)—— 规避异步报错,提升代码健壮性
异步操作中,报错、超时、状态未知是常见问题,这5种用法能优雅处理这些异常,避免页面崩溃,让代码更健壮、更易维护。
8. Promise超时处理
核心作用:给Promise设置超时时间,若超过指定时间仍未完成(未resolve/reject),则自动reject,避免异步操作“卡死”,提升用户体验。
// 核心封装:带超时的Promise
const promiseWithTimeout = (promise, ms) =>
Promise.race([
promise, // 原异步操作
// 超时定时器,超过ms毫秒后reject
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error(`Timeout after ${ms}ms`)), ms)
)
]);
// 实战用法:给接口请求设置3秒超时
async function fetchWithTimeout(url, ms = 3000) {
try {
const response = await promiseWithTimeout(fetch(url), ms);
return await response.json();
} catch (error) {
if (error.message.includes('Timeout')) {
// 处理超时逻辑(提示用户、重试等)
alert('接口请求超时,请检查网络后重试!');
} else {
console.error('接口请求失败:', error);
}
}
}
// 调用:3秒内未响应则超时
fetchWithTimeout('/api/data', 3000);
9. Promise的取消
核心作用:JavaScript原生Promise无法直接取消,但通过封装,可模拟取消逻辑(比如用户切换页面、取消请求),避免无效异步操作浪费资源。
// 核心封装:可取消的Promise
const cancellablePromise = promise => {
let isCanceled = false; // 取消标记
// 包装原Promise,添加取消逻辑
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
value => {
// 若已取消,触发reject(携带取消标记)
isCanceled ? reject({ isCanceled, value }) : resolve(value);
},
error => {
// 若已取消,触发reject(携带取消标记)
isCanceled ? reject({ isCanceled, error }) : reject(error);
}
);
});
// 返回Promise和取消方法
return {
promise: wrappedPromise,
cancel() {
isCanceled = true; // 标记为已取消
}
};
};
// 实战用法:用户取消接口请求
const { promise, cancel } = cancellablePromise(
fetch('/api/largeData').then(res => res.json())
);
// 监听取消按钮点击
document.querySelector('.cancel-btn').addEventListener('click', () => {
cancel(); // 取消Promise
console.log('请求已取消');
});
// 处理Promise结果
promise.then(data => {
console.log('请求成功:', data);
}).catch(err => {
if (err.isCanceled) {
// 处理取消逻辑(不提示错误)
} else {
console.error('请求失败:', err);
}
});
10. 检测Promise状态
核心作用:原生Promise不允许直接查询状态(pending/fulfilled/rejected),通过此方法可获取Promise的当前状态,用于调试或特殊业务逻辑。
// 核心封装:检测Promise状态
const reflectPromise = promise =>
promise.then(
value => ({ status: 'fulfilled', value }), // 成功:返回状态和结果
error => ({ status: 'rejected', error }) // 失败:返回状态和错误
);
// 实战用法:检测多个Promise的状态
const promise1 = fetch('/api/data1').then(res => res.json());
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 1000));
// 检测两个Promise的状态
Promise.all([reflectPromise(promise1), reflectPromise(promise2)]).then(results => {
results.forEach((result, index) => {
console.log(`Promise${index + 1}状态:`, result.status);
if (result.status === 'fulfilled') {
console.log('结果:', result.value);
} else {
console.log('错误:', result.error);
}
});
});
11. 确保Promise只解决一次
核心作用:避免因代码逻辑问题(比如重复调用resolve),导致Promise被多次解决(fulfilled),引发后续逻辑异常。
// 核心封装:确保只resolve一次的Promise
const onceResolvedPromise = executor => {
let isResolved = false; // 标记是否已resolve
return new Promise((resolve, reject) => {
executor(
value => {
// 只有未resolve过,才执行resolve
if (!isResolved) {
isResolved = true;
resolve(value);
}
},
reject // reject可多次调用,不影响
);
});
};
// 实战用法:避免重复提交(比如表单提交)
const submitForm = (formData) => {
return onceResolvedPromise((resolve, reject) => {
// 模拟表单提交接口
fetch('/api/submit', {
method: 'POST',
body: formData
}).then(res => res.json())
.then(data => {
resolve(data);
// 模拟重复调用resolve(无效)
resolve('重复resolve');
})
.catch(reject);
});
};
// 调用:即使内部重复resolve,也只执行一次
submitForm({ name: '张三' }).then(data => {
console.log('提交成功:', data); // 只输出一次
}).catch(err => {
console.error('提交失败:', err);
});
12. 同时执行多个异步任务并处理中途的失败
核心作用:类似Promise.allSettled,但更灵活,可自定义失败处理逻辑,适合“允许部分异步失败,同时处理成功和失败结果”的场景。
// 结合reflectPromise,处理部分失败的场景
const promises = [
fetch('/api/data1').then(res => res.json()), // 成功
fetch('/api/data2').then(res => res.json()), // 失败
fetch('/api/data3').then(res => res.json()) // 成功
];
// 先通过reflectPromise获取所有状态,再分别处理
Promise.all(promises.map(reflectPromise)).then(results => {
// 处理成功的结果
const successResults = results.filter(r => r.status === 'fulfilled').map(r => r.value);
console.log('成功的结果:', successResults);
// 处理失败的结果(比如记录日志、提示用户)
const failResults = results.filter(r => r.status === 'rejected').map(r => r.error);
failResults.forEach((err, index) => {
console.error(`任务${index + 1}失败:`, err);
// 可选:失败重试
// retryPromise(() => fetch(`/api/data${index + 2}`).then(res => res.json()), 3, 1000);
});
});
四、流程控制用法(6种)—— 优雅管理异步流程,避免混乱
异步流程混乱是前端异步开发的常见问题,比如顺序执行、条件执行、动态执行等,这6种用法能帮你精准控制异步流程,让代码逻辑更清晰。
13. 顺序执行Promise数组
核心作用:按顺序执行一组Promise,前一个异步操作完成后,再执行下一个,适合“依赖前一个结果”的场景(比如分步提交、依赖接口)。
// 核心封装:顺序执行Promise数组
const sequencePromises = promises =>
promises.reduce(
(prev, next) => prev.then(() => next()), // 前一个完成,执行下一个
Promise.resolve() // 初始值:一个已resolve的Promise
);
// 实战用法:分步提交表单(先验证、再提交、最后提示)
const step1 = () => new Promise(resolve => {
console.log('步骤1:验证表单');
setTimeout(() => resolve(), 1000);
});
const step2 = () => new Promise(resolve => {
console.log('步骤2:提交表单');
setTimeout(() => resolve(), 1500);
});
const step3 = () => new Promise(resolve => {
console.log('步骤3:提示提交成功');
setTimeout(() => resolve(), 500);
});
// 顺序执行3个步骤
sequencePromises([step1, step2, step3]).then(() => {
console.log('所有步骤执行完成');
});
14. 基于条件的Promise链
核心作用:根据条件判断,决定是否执行下一个Promise,适合“分支异步逻辑”(比如根据用户权限,决定是否获取敏感数据)。
// 核心封装:条件Promise链
const conditionalPromise = (conditionFn, promise) =>
conditionFn() ? promise : Promise.resolve(); // 条件不满足,返回已resolve的Promise
// 实战用法:根据用户权限,决定是否获取敏感数据
// 模拟判断用户是否有权限
const hasPermission = () => {
const user = JSON.parse(localStorage.getItem('user'));
return user?.role === 'admin'; // 管理员有权限
};
// 敏感数据接口(只有管理员能获取)
const fetchSensitiveData = () =>
fetch('/api/sensitive').then(res => res.json());
// 条件执行:有权限则获取,无权限则跳过
conditionalPromise(hasPermission, fetchSensitiveData())
.then(data => {
if (data) {
console.log('敏感数据:', data);
// 渲染敏感数据
} else {
console.log('无权限获取敏感数据');
}
})
.catch(err => console.error('获取失败:', err));
15. Promise的重试逻辑
核心作用:当Promise因暂时性错误(比如网络波动、接口临时不可用)失败时,自动重试指定次数,提升接口成功率。
// 核心封装:带重试的Promise
const retryPromise = (promiseFn, maxAttempts, interval) => {
return new Promise((resolve, reject) => {
// 递归重试函数
const attempt = attemptNumber => {
// 达到最大重试次数,触发reject
if (attemptNumber === maxAttempts) {
reject(new Error('Max attempts reached'));
return;
}
// 执行Promise,失败则重试
promiseFn().then(resolve).catch(() => {
// 间隔interval毫秒后,重试下一次
setTimeout(() => {
attempt(attemptNumber + 1);
}, interval);
});
};
// 开始第一次尝试
attempt(0);
});
};
// 实战用法:接口请求失败后,重试3次,每次间隔1秒
const fetchData = () => fetch('/api/data').then(res => res.json());
retryPromise(fetchData, 3, 1000)
.then(data => console.log('请求成功:', data))
.catch(err => {
console.error('重试3次仍失败:', err);
alert('请求失败,请稍后再试!');
});
16. Promise-pipeline(管道化异步操作)
核心作用:将多个异步操作串联成“管道”,前一个操作的结果作为后一个操作的输入,适合“多步骤异步处理”(比如数据获取→处理→保存)。
// 核心封装:Promise管道函数
const promisePipe = (...fns) => value =>
fns.reduce((p, f) => p.then(f), Promise.resolve(value));
// 实战用法:数据处理管道(获取数据→格式化→保存)
// 步骤1:获取数据
const fetchData = () => fetch('/api/rawData').then(res => res.json());
// 步骤2:格式化数据
const formatData = (data) => {
return new Promise(resolve => {
const formatted = data.map(item => ({
id: item.id,
name: item.name,
time: new Date(item.time).toLocaleString()
}));
resolve(formatted);
});
};
// 步骤3:保存数据
const saveData = (data) => fetch('/api/save', {
method: 'POST',
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json());
// 管道化执行:fetchData → formatData → saveData
const dataPipeline = promisePipe(fetchData, formatData, saveData);
dataPipeline().then(res => {
console.log('数据处理完成:', res);
}).catch(err => {
console.error('数据处理失败:', err);
});
17. 动态生成Promise链
核心作用:根据不同条件,动态生成异步操作链,适合“不确定异步步骤数量”的场景(比如根据用户选择,执行不同的异步操作)。
// 实战用法:根据用户选择的操作,动态生成Promise链
// 模拟用户选择的操作(可动态变化)
const userActions = [
() => fetch('/api/action1').then(res => res.json()),
() => fetch('/api/action2').then(res => res.json()),
() => fetch('/api/action3').then(res => res.json())
];
// 动态生成Promise链
const promiseChain = userActions.reduce((chain, currentTask) => {
return chain.then(currentTask); // 依次添加异步任务到链中
}, Promise.resolve()); // 初始值
// 执行动态生成的Promise链
promiseChain.then(results => {
console.log('所有动态操作完成:', results);
}).catch(err => {
console.error('动态操作失败:', err);
});
18. 连续获取不确定数量的数据页
核心作用:获取分页数据时,不知道总页数,通过递归方式,自动获取所有分页数据,直到没有下一页,适合“无限滚动”“批量导出”场景。
// 核心封装:递归获取所有分页数据
async function fetchPages(apiEndpoint, page = 1, allResults = []) {
try {
// 发起分页请求
const response = await fetch(`${apiEndpoint}?page=${page}`);
const data = await response.json();
// 合并当前页数据
const newResults = allResults.concat(data.results);
// 判断是否有下一页,有则继续递归,无则返回所有数据
if (data.nextPage) {
return fetchPages(apiEndpoint, page + 1, newResults);
} else {
return newResults;
}
} catch (error) {
console.error('获取分页数据失败:', error);
throw error; // 抛出错误,让调用者处理
}
}
// 实战用法:获取所有用户分页数据(不知道总页数)
fetchPages('/api/users')
.then(allUsers => {
console.log('所有用户数据:', allUsers);
// 渲染所有用户数据
})
.catch(err => {
alert('获取用户数据失败,请重试!');
});
五、高级进阶用法(5种)—— 提升编码上限,面试加分项
这5种用法属于进阶技巧,在复杂项目中经常用到,掌握它们,能让你在异步开发中更游刃有余,也是前端面试的高频加分项。
19. 使用Generators管理异步流程
核心作用:将async/await与Generators配合,创建可控制的异步流程管理器,适合“复杂异步流程控制”(比如暂停、继续、中断异步操作)。
// 基础用法:Generator配合Promise管理流程
function* asyncGenerator() {
console.log('开始执行异步流程');
// yield后面跟Promise,暂停等待Promise完成
const result1 = yield fetch('/api/data1').then(res => res.json());
console.log('第一个异步操作完成:', result1);
// 第二个异步操作,依赖第一个的结果
const result2 = yield fetch(`/api/data2?userId=${result1.id}`).then(res => res.json());
console.log('第二个异步操作完成:', result2);
return result2; // 返回最终结果
}
// 执行Generator函数
function runGenerator(generator) {
const iterator = generator();
const handle = (result) => {
if (result.done) return result.value;
// 处理yield返回的Promise
result.value.then(res => {
handle(iterator.next(res));
}).catch(err => {
iterator.throw(err);
});
};
handle(iterator.next());
}
// 调用:执行异步流程
runGenerator(asyncGenerator);
20. 流式处理大型数据集
核心作用:处理大型数据集时,避免一次性加载所有数据导致内存过载,通过“流式处理”(分块处理),提升性能和用户体验。
// 实战用法:流式处理大型Excel数据(分块读取、分块处理)
async function processLargeDataSet(dataSet) {
// dataSet:大型数据集(比如10万条数据),按块分割
for (const dataChunk of dataSet) {
// 分块处理数据(每个块单独异步处理)
const processedChunk = await processData(dataChunk); // 处理单个块
// 分块保存数据,避免一次性保存导致卡顿
await saveProcessedData(processedChunk);
console.log('处理完成一个数据块');
}
console.log('所有大型数据处理完成');
}
// 模拟分块处理函数
function processData(chunk) {
return new Promise(resolve => {
// 模拟数据处理(比如格式化、过滤)
const processed = chunk.map(item => item * 2);
setTimeout(() => resolve(processed), 500);
});
}
// 模拟分块保存函数
function saveProcessedData(chunk) {
return fetch('/api/saveChunk', {
method: 'POST',
body: JSON.stringify(chunk),
headers: { 'Content-Type': 'application/json' }
}).then(res => res.json());
}
// 模拟大型数据集(10个块,每个块1万条数据)
const largeDataSet = Array(10).fill(0).map(() => Array(10000).fill(0).map((_, i) => i));
// 流式处理
processLargeDataSet(largeDataSet);
21. 使用Promise实现简易的异步锁
核心作用:在多线程/多异步场景中,确保同一时间只有一个异步操作执行(比如防止重复提交、并发修改数据),避免数据冲突。
// 核心封装:简易异步锁
let lock = Promise.resolve(); // 初始锁:已解锁状态
// 获取锁
const acquireLock = () => {
let release;
// 等待锁释放的Promise
const waitLock = new Promise(resolve => {
release = resolve; // 释放锁的方法
});
// 尝试获取锁:当前锁释放后,返回释放锁的方法
const tryAcquireLock = lock.then(() => release);
// 更新锁状态:当前锁变为等待状态
lock = waitLock;
return tryAcquireLock;
};
// 实战用法:防止重复提交表单
async function submitForm(formData) {
// 获取锁,获取成功才能执行提交
const release = await acquireLock();
try {
console.log('开始提交表单');
// 模拟表单提交(异步操作)
const res = await fetch('/api/submit', {
method: 'POST',
body: formData
}).then(res => res.json());
console.log('表单提交成功:', res);
return res;
} catch (err) {
console.error('表单提交失败:', err);
} finally {
// 无论成功失败,都释放锁
release();
console.log('锁已释放');
}
}
// 模拟多次点击提交按钮(只会执行一次提交)
submitForm({ name: '张三' });
submitForm({ name: '张三' });
submitForm({ name: '张三' });
22. 组合多个Promise操作为一个函数
核心作用:将多个相关的Promise操作合并为一个函数,实现代码复用,减少冗余,提升代码可维护性。
// 实战用法:组合“获取数据+处理数据”为一个函数
// 处理数据的辅助函数
const processData = (data) => {
return new Promise(resolve => {
// 模拟数据处理(过滤、格式化)
const processed = data.filter(item => item.status === 1).map(item => ({
id: item.id,
name: item.name,
time: item.createTime
}));
resolve(processed);
});
};
// 组合多个Promise操作
const fetchDataAndProcess = async url => {
// 步骤1:获取数据
const response = await fetch(url);
const rawData = await response.json();
// 步骤2:处理数据
const processedData = await processData(rawData);
// 返回处理后的结果
return processedData;
};
// 复用函数:获取不同接口的数据并处理
fetchDataAndProcess('/api/users')
.then(users => console.log('处理后的用户数据:', users))
.catch(err => console.error('处理失败:', err));
fetchDataAndProcess('/api/goods')
.then(goods => console.log('处理后的商品数据:', goods))
.catch(err => console.error('处理失败:', err));
23. 处理可选的异步操作
核心作用:处理“可选的异步操作”,当条件满足时执行异步操作,不满足时返回默认值,避免多余的错误处理。
// 核心封装:处理可选异步操作
async function optionallyAsyncTask(condition, asyncOperation, fallbackValue) {
if (condition) {
// 条件满足,执行异步操作
return await asyncOperation;
} else {
// 条件不满足,返回默认值
return fallbackValue;
}
}
// 实战用法:根据用户是否登录,决定是否获取用户个性化数据
const isLogin = () => !!localStorage.getItem('token'); // 判断用户是否登录
const fetchPersonalData = () => fetch('/api/personal').then(res => res.json()); // 个性化数据接口
// 调用:登录则获取个性化数据,未登录则返回默认数据
optionallyAsyncTask(isLogin(), fetchPersonalData(), {
name: '游客',
recommend: []
}).then(data => {
console.log('用户数据:', data);
// 渲染用户数据(无论是否登录,都有默认值,避免报错)
document.querySelector('.user-info').textContent = JSON.stringify(data);
});
六、Promise高频避坑总结(新手必记,少踩雷)
很多新手学完Promise,一写代码就报错,不是语法错了,就是用法不对,整理了5个高频坑,记牢就能避开!
- 坑1:忘记处理Promise的reject——Promise失败时,若未用catch捕获,会报未捕获错误,导致页面崩溃,务必给每个Promise添加catch处理。
- 坑2:混淆Promise.all和Promise.allSettled——前者只要一个失败就整体失败,后者无论成败都会等待所有执行完成,按需选择。
- 坑3:认为Promise可以直接取消——原生Promise无法取消,需通过封装(添加取消标记)模拟取消逻辑。
- 坑4:await后面跟非Promise值——await只能等待Promise或async函数,若跟普通值,会直接返回该值,相当于同步执行,无意义。
- 坑5:重复调用resolve/reject——Promise状态一旦改变就无法逆转,重复调用resolve/reject无效,还可能导致逻辑混乱,可用onceResolvedPromise避免。
七、面试高频考点(新手必记,轻松拿捏面试官)
Promise是前端面试高频考点,不用死记硬背,记住这5个核心问题,面试时直接套用即可:
- Q1:Promise有哪三种状态?状态能否逆转? A:pending(等待中)、fulfilled(成功)、rejected(失败);状态一旦改变(pending→fulfilled或pending→rejected),就无法逆转。
- Q2:Promise.all和Promise.race的区别? A:Promise.all等待所有Promise成功,一个失败则整体失败;Promise.race只取最快完成的Promise结果,无论成功还是失败。
- Q3:async/await和Promise的关系? A:asyncawait是Promise的语法糖,async函数本质上返回一个Promise,await只能在async函数中使用,用于等待Promise完成。
- Q4:如何实现Promise的取消? A:原生Promise无法直接取消,可通过添加取消标记(isCanceled),在Promise.then/catch中判断标记,实现模拟取消。
- Q5:Promise的错误捕获有哪些方式? A:两种方式:一是给每个Promise添加.catch();二是在async/await中用try/catch捕获所有异步错误。
八、最后说几句掏心窝的话
Promise不难,核心就是“异步状态管理”,23种用法看似多,但不用死记硬背——日常开发中,高频使用的也就10种左右(async/await、Promise.all、超时处理、顺序执行等),剩下的可作为储备,用到时翻这篇文章即可。
它不是前端进阶的“加分项”,而是“必备项”——现在前端开发几乎所有异步操作都离不开Promise,不学真的会被淘汰。这篇文章整理了所有用法的实战代码、通俗解读和避坑细节,代码可直接复制练习,建议收藏起来,开发时遇到问题就翻一翻,慢慢就熟练了。
如果觉得有用,记得点赞+收藏,关注我,后续更新更多前端小白干货,一起从新手进阶成资深开发者💪