【JavaScript】Promise 完全指南
·
Promise 完全指南(核心概念+实战用法+避坑)
Promise 是 ES6 引入的异步编程解决方案,用于解决传统回调函数嵌套导致的「回调地狱」问题,统一异步操作的处理范式,让异步代码更易读、易维护。它本质是一个「状态机」,描述异步操作的最终状态(成功/失败),并提供统一的结果/错误处理方式。
一、核心概念
1. Promise 的核心定位
解决异步编程的痛点:
- 回调地狱:多层异步回调嵌套(如
ajax1(() => { ajax2(() => { ajax3() }) })),代码臃肿、难以调试; - 异步操作标准化:统一异步操作的「成功/失败」处理逻辑,替代混乱的回调写法。
2. Promise 的三种状态(核心!状态不可逆)
| 状态 | 含义 | 能否切换 |
|---|---|---|
pending | 进行中(初始状态) | 可切换为 fulfilled 或 rejected |
fulfilled | 已成功(已解决) | 一旦切换,状态永久固定,不可再变 |
rejected | 已失败(已拒绝) | 一旦切换,状态永久固定,不可再变 |
关键:Promise 状态一旦从
pending变为fulfilled/rejected,就无法再修改(比如调用resolve后再调用reject无效)。
3. 核心回调函数
创建 Promise 时需传入一个「执行器函数」,接收两个内置回调:
resolve:异步操作成功时调用,将状态改为fulfilled,并传递成功结果;reject:异步操作失败时调用,将状态改为rejected,并传递错误信息。
二、Promise 基本用法
1. 基础创建与执行
// 1. 创建 Promise 实例
const promise = new Promise((resolve, reject) => {
// 模拟异步操作(如接口请求、定时器、文件读取)
setTimeout(() => {
const randomNum = Math.random();
if (randomNum > 0.5) {
// 成功:调用 resolve,传递结果
resolve({ code: 200, data: `随机数:${randomNum}` });
} else {
// 失败:调用 reject,传递错误
reject({ code: 500, msg: `随机数过小:${randomNum}` });
}
}, 1000);
});
// 2. 处理 Promise 结果(then 处理成功,catch 处理失败)
promise
.then((res) => {
console.log('成功:', res); // 状态为 fulfilled 时执行
})
.catch((err) => {
console.log('失败:', err); // 状态为 rejected 时执行
})
.finally(() => {
console.log('无论成功/失败,都会执行'); // 可选:最终回调(ES2018 新增)
});
2. 关键特性:异步执行
Promise 执行器函数立即执行,但 then/catch/finally 中的回调会放入「微任务队列」,等待同步代码执行完成后再执行:
console.log('同步代码开始');
const p = new Promise((resolve) => {
console.log('执行器函数(同步)');
resolve('异步结果');
});
p.then((res) => {
console.log('then 回调(微任务):', res);
});
console.log('同步代码结束');
// 输出顺序:
// 同步代码开始 → 执行器函数(同步) → 同步代码结束 → then 回调(微任务):异步结果
三、Promise 常用 API
1. 实例方法(Promise 实例调用)
| 方法 | 作用 |
|---|---|
then() | 处理成功状态:接收两个参数(可选),第一个处理 fulfilled,第二个处理 rejected |
catch() | 处理失败状态:捕获 rejected 状态或 then 中抛出的错误 |
finally() | 最终回调:无论成功/失败都会执行,无参数(无法获取结果/错误) |
示例:then 的第二个参数(不推荐,优先用 catch)
promise.then(
(res) => console.log('成功', res), // 成功回调
(err) => console.log('失败', err) // 失败回调(等价于 catch,但无法捕获前一个 then 的错误)
);
2. 静态方法(Promise 构造函数调用)
(1)Promise.resolve():快速创建「已成功」的 Promise
// 场景:将非 Promise 值转为 Promise(统一异步接口)
const p1 = Promise.resolve(123); // 等价于 new Promise(resolve => resolve(123))
p1.then(res => console.log(res)); // 123
// 若参数是 Promise,直接返回该 Promise
const p2 = Promise.resolve(p1);
console.log(p1 === p2); // true
(2)Promise.reject():快速创建「已失败」的 Promise
const p = Promise.reject('出错了');
p.catch(err => console.log(err)); // 出错了
(3)Promise.all():并行执行多个 Promise,「全部成功」才返回
- 入参:Promise 数组(或可迭代对象);
- 结果:返回新 Promise,成功结果为数组(顺序与入参一致);
- 失败:只要有一个 Promise 失败,立即返回该失败结果(其余 Promise 仍会执行,但结果被忽略)。
// 示例:并行请求多个接口
const p1 = Promise.resolve('接口1结果');
const p2 = Promise.resolve('接口2结果');
const p3 = Promise.resolve('接口3结果');
Promise.all([p1, p2, p3])
.then(res => console.log('全部成功:', res)) // ['接口1结果', '接口2结果', '接口3结果']
.catch(err => console.log('有失败:', err));
// 若 p2 失败:
const p2Error = Promise.reject('接口2失败');
Promise.all([p1, p2Error, p3])
.catch(err => console.log(err)); // 接口2失败(p1、p3 仍执行,但结果被忽略)
(4)Promise.race():并行执行多个 Promise,「第一个完成」就返回
- 入参:Promise 数组;
- 结果:返回新 Promise,结果为「第一个完成(成功/失败)」的 Promise 结果。
// 场景:请求超时控制(5秒未响应则触发超时)
const request = new Promise((resolve) => setTimeout(() => resolve('请求成功'), 6000));
const timeout = new Promise((_, reject) => setTimeout(() => reject('请求超时'), 5000));
Promise.race([request, timeout])
.then(res => console.log(res))
.catch(err => console.log(err)); // 请求超时(因为 timeout 先完成)
(5)Promise.allSettled():并行执行多个 Promise,「全部完成」后返回(无论成败)
- ES2020 新增,解决
all「一错全错」的问题; - 结果:返回新 Promise,成功结果为数组,每个元素包含
status(fulfilled/rejected)和value/reason。
const p1 = Promise.resolve('成功1');
const p2 = Promise.reject('失败2');
Promise.allSettled([p1, p2])
.then(res => {
console.log(res);
// 输出:
// [
// { status: 'fulfilled', value: '成功1' },
// { status: 'rejected', reason: '失败2' }
// ]
});
(6)Promise.any():并行执行多个 Promise,「第一个成功」就返回
- ES2021 新增,与
race相反:只关注第一个成功的 Promise; - 失败:所有 Promise 都失败时,返回
AggregateError(包含所有错误)。
const p1 = Promise.reject('失败1');
const p2 = Promise.resolve('成功2');
const p3 = Promise.reject('失败3');
Promise.any([p1, p2, p3])
.then(res => console.log(res)) // 成功2
.catch(err => console.log(err)); // 仅所有失败时执行
四、链式调用(解决回调地狱)
1. 回调地狱 vs Promise 链式
// 传统回调地狱(多层嵌套)
ajax('url1', (res1) => {
ajax('url2?id=' + res1.id, (res2) => {
ajax('url3?name=' + res2.name, (res3) => {
console.log('最终结果:', res3);
}, (err3) => console.log('err3', err3));
}, (err2) => console.log('err2', err2));
}, (err1) => console.log('err1', err1));
// Promise 链式调用(扁平化)
function request(url) {
return new Promise((resolve, reject) => {
ajax(url, resolve, reject);
});
}
request('url1')
.then(res1 => request('url2?id=' + res1.id)) // 返回新 Promise,链式继续
.then(res2 => request('url3?name=' + res2.name))
.then(res3 => console.log('最终结果:', res3))
.catch(err => console.log('任意一步出错:', err)); // 统一捕获所有错误
2. 链式调用关键:then 必须返回 Promise
then 中返回非 Promise 值时,会被 Promise.resolve() 包装为成功的 Promise,确保链式可延续:
Promise.resolve(1)
.then(res => res + 1) // 返回 2 → 包装为 Promise.resolve(2)
.then(res => res * 2) // 返回 4 → 包装为 Promise.resolve(4)
.then(res => console.log(res)); // 4
五、错误处理
1. catch 捕获所有上游错误
catch 能捕获:
- Promise 执行器中调用
reject(); then/finally中抛出的同步错误;- 上游 Promise 链中的任何错误。
new Promise((resolve, reject) => {
reject('执行器失败'); // 触发 catch
})
.then(res => {
console.log(res);
throw new Error('then 中抛出错误'); // 若执行到这里,也会触发 catch
})
.catch(err => console.log('捕获错误:', err)); // 捕获「执行器失败」或「then 抛出的错误」
2. async/await 结合 Promise(实战主流写法)
async/await 是 ES2017 语法糖,基于 Promise 实现,让异步代码「同步化」:
// 封装异步请求为 Promise
function fetchData(url) {
return new Promise((resolve) => {
setTimeout(() => resolve(`数据:${url}`), 1000);
});
}
// async/await 调用(需包裹在 async 函数中)
async function getData() {
try {
const res1 = await fetchData('url1'); // 等待 Promise 成功
const res2 = await fetchData(`url2?${res1}`);
console.log('最终数据:', res2);
} catch (err) {
console.log('错误:', err); // 捕获所有 await 中的 Promise 失败
}
}
getData();
六、实战场景示例
1. 封装接口请求(Axios 本质是 Promise)
// 模拟 Axios 封装
function request(options) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', options.url);
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
reject({ status: xhr.status, msg: '请求失败' });
}
};
xhr.onerror = () => reject({ msg: '网络错误' });
xhr.send(options.data ? JSON.stringify(options.data) : null);
});
}
// 使用
request({ url: '/api/user', method: 'POST', data: { id: 1 } })
.then(res => console.log(res))
.catch(err => console.log(err));
2. 并行请求 + 数据整合
// 并行请求用户和订单数据,整合后返回
async function getUserAndOrder(userId) {
try {
// 并行执行两个 Promise(比串行快)
const [user, order] = await Promise.all([
request({ url: `/api/user/${userId}` }),
request({ url: `/api/order/${userId}` })
]);
return { ...user, orderList: order.list }; // 整合数据
} catch (err) {
console.log('请求失败:', err);
return null;
}
}
getUserAndOrder(1).then(res => console.log(res));
七、常见坑点与避坑
1. 忘记 return Promise 导致链式断裂
// 错误写法:then 中未返回 Promise,后续 then 拿到 undefined
Promise.resolve(1)
.then(res => {
res + 1; // 无 return,等价于 return undefined
})
.then(res => console.log(res)); // undefined
// 正确写法:return 结果(或 Promise)
Promise.resolve(1)
.then(res => res + 1) // return 2
.then(res => console.log(res)); // 2
2. 状态不可逆导致的逻辑错误
new Promise((resolve, reject) => {
resolve('成功'); // 状态变为 fulfilled
reject('失败'); // 无效!状态已固定,不会触发 catch
})
.then(res => console.log(res)) // 成功
.catch(err => console.log(err)); // 不执行
3. Promise.all 「一错全错」的风险
若需兼容部分请求失败的场景,改用 Promise.allSettled():
// 需求:即使部分接口失败,也要获取成功的结果
async function getMultiData(urls) {
const results = await Promise.allSettled(urls.map(url => request({ url })));
// 过滤出成功的结果
const successData = results
.filter(item => item.status === 'fulfilled')
.map(item => item.value);
return successData;
}
4. 未捕获的 Promise 错误
Promise 错误若未用 catch 捕获,会触发控制台报错(Node.js 甚至会终止进程):
// 错误:无 catch 捕获
new Promise((_, reject) => reject('出错了'));
// 正确:始终添加 catch,或用 unhandledrejection 全局监听
window.addEventListener('unhandledrejection', (e) => {
e.preventDefault(); // 阻止控制台报错
console.log('全局捕获 Promise 错误:', e.reason);
});
八、总结
1. 核心价值
- 解决回调地狱,让异步代码扁平化;
- 统一异步操作的成功/失败处理逻辑;
- 提供丰富的静态方法(all/race 等),满足多异步场景需求。
2. 实战选型
| 场景 | 推荐 API |
|---|---|
| 单个异步操作 | 基础 Promise + then/catch |
| 多个异步并行,需全部成功 | Promise.all |
| 多个异步并行,需全部完成(无论成败) | Promise.allSettled |
| 多个异步并行,取第一个成功 | Promise.any |
| 异步操作超时控制 | Promise.race |
| 简化异步代码书写 | async/await(基于 Promise) |
3. 关键原则
- 始终为 Promise 添加
catch错误处理; - 链式调用时确保
then返回值(尤其是 Promise); - 多异步场景根据「是否允许部分失败」选择
all/allSettled/any。
Promise 是现代 JS 异步编程的基础,axios、fetch(封装后)、Node.js 异步 API 等均基于 Promise 实现,掌握它是理解 async/await、微任务等高级概念的核心前提。
更多推荐


所有评论(0)