Promise 从入门到精通:彻底解决前端异步回调问题
🚀 一、Promise 核心介绍
1. 什么是 Promise?
Promise 是 ES6(ECMAScript 2015)引入的异步编程解决方案,是一个用于封装异步操作并管理其结果的对象。它解决了传统异步编程中回调地狱(Callback Hell) 的嵌套问题,让异步代码的逻辑更清晰、更易维护,同时提供了统一的异步错误处理机制,是现代前端异步编程的基础(async/await 正是基于 Promise 实现的语法糖)。
简单来说:Promise 就像一个异步操作的“承诺” —— 异步操作执行前,它处于等待状态;异步操作完成后,它会兑现“承诺”(返回成功结果)或拒绝“承诺”(返回失败原因),且这个结果一旦确定就不可修改。
2. 核心特性
-
状态不可逆:Promise 有且仅有三种状态,状态一旦改变,就会永久保持该状态,不会再发生变化;
-
统一的异步范式:将不同类型的异步操作(AJAX、定时器、文件操作等)封装为统一的 Promise 接口,解决异步操作格式不统一的问题;
-
链式调用:支持
.then()、.catch()、.finally() 链式调用,替代多层回调嵌套,让异步代码线性化;
-
集中错误处理:支持全局错误捕获,一个
.catch() 可捕获链式调用中所有前置操作的错误,避免单独处理每个异步操作的异常;
-
非阻塞执行:Promise 封装的异步操作不会阻塞浏览器主线程,保证页面交互的流畅性;
-
一次性执行:Promise 内部的异步操作一旦执行,就会完成整个流程,不会被重复触发。
3. 解决的核心问题:回调地狱
传统异步编程依赖回调函数,当多个异步操作存在顺序依赖(后一个操作依赖前一个操作的结果)时,会出现多层回调嵌套,形成回调地狱,代码表现为「层层缩进的金字塔结构」,存在可读性差、维护困难、错误无法统一捕获等问题。
回调地狱示例(定时器嵌套):
// 需求:依次执行三个异步操作,后一个操作等待前一个完成
setTimeout(() => {
console.log('第一步操作完成');
setTimeout(() => {
console.log('第二步操作完成');
setTimeout(() => {
console.log('第三步操作完成');
// 更多嵌套...
}, 1000);
}, 1000);
}, 1000);
Promise 改造后(链式调用,无嵌套):
// 封装Promise版定时器
function delay(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
// 线性链式调用,逻辑清晰
delay(1000)
.then(() => console.log('第一步操作完成'))
.then(() => delay(1000))
.then(() => console.log('第二步操作完成'))
.then(() => delay(1000))
.then(() => console.log('第三步操作完成'));
对比可见:Promise 彻底摆脱了回调嵌套,让异步代码的执行流程与同步代码一致,大幅提升了代码的可读性和可维护性。
4. Promise 与传统异步方案的对比
| 特性 |
Promise |
传统回调函数 |
async/await |
| 代码结构 |
链式调用,无嵌套 |
多层嵌套,回调地狱 |
同步化写法,最简洁 |
| 错误处理 |
统一 .catch() 捕获 |
每个回调单独处理错误 |
try/catch 捕获,更符合直觉 |
| 异步流程控制 |
原生支持(.then 链式) |
手动嵌套控制 |
同步流程控制(基于Promise) |
| 学习成本 |
中等 |
低(但复杂场景易出错) |
高(需先掌握Promise) |
| 依赖关系 |
无(ES6原生) |
无 |
依赖Promise(ES7) |
| 适用场景 |
简单至复杂异步流程 |
简单异步操作(无依赖) |
复杂异步流程、多请求依赖 |
核心结论:async/await 是 Promise 的语法糖,传统回调是基础,Promise 是现代前端异步编程的核心桥梁。
🎯 二、Promise 核心概念
1. 三种状态与状态转换
Promise 的核心是状态管理,其一生仅有三种状态,且状态转换不可逆、仅能发生一次,这是 Promise 解决异步不确定性的关键。
三种基础状态
-
pending(进行中):初始状态,异步操作尚未完成,此时既未成功也未失败;
-
fulfilled(已成功):异步操作顺利完成,Promise 兑现承诺,返回成功结果;
-
rejected(已失败):异步操作执行失败,Promise 拒绝承诺,返回失败原因(错误对象)。
唯一的两种状态转换路径
Promise 只能从初始状态向最终状态转换,且转换后状态永久固定,无法反向转换,也无法在成功/失败之间切换:
- 路径1:
pending → fulfilled(异步操作成功,调用 resolve() 触发);
- 路径2:
pending → rejected(异步操作失败,调用 reject() 触发)。
核心注意:一旦状态变为 fulfilled 或 rejected,就会成为定型状态(settled),后续再调用 resolve() 或 reject() 均无效。
2. 两个核心回调函数
Promise 构造函数接收一个执行器函数(executor),该函数会在 Promise 创建时立即同步执行,且接收两个内置的核心回调函数作为参数,用于手动触发状态转换:
-
resolve(result):将 Promise 状态从
pending 转为 fulfilled,并将异步操作的成功结果传递出去(result 可为任意类型:基本类型、对象、数组,甚至另一个 Promise);
-
reject(reason):将 Promise 状态从
pending 转为 rejected,并将异步操作的失败原因传递出去(reason 通常为 Error 对象,便于后续错误捕获和栈追踪)。
核心注意:执行器函数是同步执行的,内部的异步操作是异步执行的,这是容易混淆的关键点,示例:
const p = new Promise((resolve, reject) => {
console.log('Promise 执行器函数:同步执行'); // 立即输出
setTimeout(() => {
console.log('异步操作:异步执行'); // 1秒后输出
resolve('异步操作成功');
}, 1000);
});
console.log('Promise 创建完成'); // 执行器后立即输出
输出顺序:Promise 执行器函数 → Promise 创建完成 → 异步操作:异步执行。
3. 定型状态(Settled)
定型状态是 Promise 的最终状态统称,指 Promise 已完成状态转换,不再处于 pending 状态,包含两种情况:
-
fulfilled(已成功)是定型状态;
-
rejected(已失败)也是定型状态。
后续的 .then()、.catch()、.finally() 方法,本质都是监听 Promise 的定型状态,一旦状态定型,就会执行对应的回调函数。
📁 三、Promise 实例的核心方法
Promise 实例提供了 .then()、.catch()、.finally() 三个核心原型方法,均支持链式调用(核心原因:每个方法执行后都会返回一个新的 Promise 实例,而非原实例),这是解决回调地狱的关键。
所有方法的回调函数都会被加入微任务队列,在浏览器主线程同步代码执行完成后、宏任务执行前执行(Promise 属于微任务,这是事件循环的重要知识点)。
1. then(onFulfilled, onRejected):处理成功/失败结果
.then() 是 Promise 最核心的方法,用于监听 Promise 的定型状态,接收两个可选的回调参数:
-
onFulfilled(result):可选,Promise 状态为
fulfilled 时执行,参数 result 是 resolve() 传递的成功结果;
-
onRejected(reason):可选,Promise 状态为
rejected 时执行,参数 reason 是 reject() 传递的失败原因。
核心特性
-
链式调用基础:
.then() 执行后返回新的 Promise 实例,新实例的状态由当前回调函数的执行结果决定;
-
参数可选:可只传
onFulfilled(仅处理成功),也可只传 onRejected(但更推荐用 .catch() 处理失败);
-
值的传递:若回调函数返回一个普通值(非 Promise、非抛出错误),新 Promise 会以
fulfilled 状态将该值传递给下一个 .then();
-
错误透传:若回调函数抛出错误,新 Promise 会以
rejected 状态将错误传递给后续的 .catch() 或 .then() 的 onRejected。
基础使用示例
const p = new Promise((resolve, reject) => {
const random = Math.random();
if (random > 0.5) {
resolve(`成功:随机数${random.toFixed(2)}`);
} else {
reject(new Error(`失败:随机数${random.toFixed(2)}`));
}
});
// 处理成功和失败
p.then(
(res) => console.log('then成功回调:', res),
(err) => console.log('then失败回调:', err.message)
);
链式调用示例(核心)
// 链式调用:依次处理,值的传递
new Promise((resolve) => resolve(1))
.then((num) => {
console.log('第一步:', num); // 1
return num + 1; // 返回普通值,传递给下一个then
})
.then((num) => {
console.log('第二步:', num); // 2
return new Promise(resolve => setTimeout(() => resolve(num + 1), 1000)); // 返回Promise
})
.then((num) => {
console.log('第三步:', num); // 3(1秒后)
throw new Error('主动抛出错误'); // 抛出错误,触发后续catch
})
.then(
(num) => console.log('第四步:', num), // 不会执行
(err) => console.log('then捕获错误:', err.message) // 可捕获,但推荐用catch
);
2. catch(onRejected):专门处理失败结果
.catch() 是 .then(null, onRejected) 的语法糖,专门用于捕获 Promise 链式调用中所有前置操作的错误(包括 reject() 触发的失败、回调函数中抛出的错误、同步代码错误),是 Promise 错误处理的推荐方式。
核心特性
-
全局错误捕获:一个
.catch() 可捕获链式调用中所有前置 .then() 的错误,无需在每个 .then() 中单独处理;
-
链式调用:
.catch() 执行后也会返回新的 Promise 实例,若在 .catch() 中返回普通值,可继续链式调用 .then();
-
错误兜底:若 Promise 链式调用中没有
.catch(),未捕获的错误会触发浏览器的 unhandledrejection 事件,导致控制台报错(生产环境需避免);
-
捕获范围:不仅捕获 Promise 自身的
reject(),还捕获所有回调函数中的同步错误和异步错误(如回调中调用未定义的变量)。
基础使用示例
new Promise((resolve, reject) => {
reject(new Error('异步操作失败'));
})
.then((res) => console.log('成功:', res)) // 不会执行
.catch((err) => console.log('catch捕获错误:', err.message)); // 输出:异步操作失败
全局错误捕获示例(链式核心)
// 一个catch捕获所有前置错误
new Promise((resolve) => {
resolve(1);
})
.then((num) => {
console.log(num);
a++; // 引用未定义变量,同步错误
})
.then((num) => console.log(num)) // 不会执行
.catch((err) => {
console.log('catch捕获所有错误:', err.message); // 输出:a is not defined
return 10; // 返回普通值,继续链式调用
})
.then((num) => console.log('catch后继续执行:', num)); // 输出:10
3. finally(onFinally):处理最终收尾操作
.finally() 是 ES2018 引入的方法,用于指定无论 Promise 状态是成功还是失败,都会执行的收尾操作,比如关闭加载弹窗、释放资源、取消定时器等。
核心特性
-
无参数:
.finally() 的回调函数不接收任何参数,因为它无需关心 Promise 的执行结果(成功/失败),仅做通用收尾;
-
必然执行:无论 Promise 是
fulfilled 还是 rejected,也无论链式调用中是否有 .catch(),.finally() 都会执行;
-
链式调用:
.finally() 也会返回新的 Promise 实例,且会透传前置 Promise 的成功结果或失败原因(即不改变原有的结果);
-
无返回值影响:
.finally() 的回调函数返回的值会被忽略,不会影响后续链式调用的参数。
基础使用示例(最常用场景:关闭加载)
// 模拟接口请求
function requestData() {
return new Promise((resolve, reject) => {
console.log('显示加载弹窗');
setTimeout(() => {
const isSuccess = Math.random() > 0.5;
if (isSuccess) {
resolve('请求成功:获取到数据');
} else {
reject(new Error('请求失败:网络错误'));
}
}, 1000);
});
}
// 执行请求,finally关闭加载
requestData()
.then((res) => console.log(res))
.catch((err) => console.log(err.message))
.finally(() => {
console.log('关闭加载弹窗'); // 无论成功/失败,都会执行
});
透传结果示例
// finally透传成功结果
new Promise((resolve) => resolve('成功数据'))
.finally(() => {
console.log('执行finally');
return 'finally的返回值'; // 会被忽略
})
.then((res) => console.log('最终结果:', res)); // 输出:成功数据
// finally透传失败原因
new Promise((reject) => reject(new Error('失败原因')))
.finally(() => console.log('执行finally'))
.catch((err) => console.log('最终错误:', err.message)); // 输出:失败原因
🚀 四、Promise 构造函数的静态方法
Promise 构造函数本身提供了多个静态方法,用于快速创建 Promise 实例或批量管理多个 Promise 实例,是处理多异步操作流程控制的核心,日常开发中使用频率极高。
1. Promise.resolve(value):快速创建成功的 Promise
用于快速创建一个状态为 fulfilled 的 Promise 实例,等价于 new Promise(resolve => resolve(value)),适合将普通值、非 Promise 异步操作转为 Promise 格式,实现统一的异步接口。
核心特性
- 若参数
value 是普通值(基本类型、对象),新 Promise 直接以该值为成功结果;
- 若参数
value 是另一个 Promise 实例,则直接返回该实例(状态和结果均透传);
- 若参数
value 是具有 then 方法的对象(thenable),则会执行其 then 方法,根据 then 方法的执行结果确定新 Promise 的状态。
使用示例
// 1. 传入普通值
const p1 = Promise.resolve(123);
p1.then(res => console.log(p1)); // 123
// 2. 传入Promise实例
const p2 = new Promise(resolve => resolve('原Promise'));
const p3 = Promise.resolve(p2);
console.log(p2 === p3); // true(直接返回原实例)
// 3. 传入thenable对象
const thenable = {
then(resolve) {
resolve('thenable执行成功');
}
};
Promise.resolve(thenable).then(res => console.log(res)); // thenable执行成功
2. Promise.reject(reason):快速创建失败的 Promise
用于快速创建一个状态为 rejected 的 Promise 实例,等价于 new Promise((resolve, reject) => reject(reason)),适合快速抛出异步错误。
核心特性
- 无论参数
reason 是什么类型(普通值、Promise 实例、thenable 对象),都会直接作为失败原因传递给 .catch(),不会透传 Promise 实例的状态(与 Promise.resolve 不同)。
使用示例
// 1. 传入普通错误
Promise.reject('简单错误').catch(err => console.log(err)); // 简单错误
// 2. 传入Error对象(推荐)
Promise.reject(new Error('标准错误')).catch(err => console.log(err.message)); // 标准错误
// 3. 传入Promise实例(不会透传,直接作为原因)
const p = Promise.resolve('成功的Promise');
Promise.reject(p).catch(err => console.log(err === p)); // true
3. Promise.all(iterable):所有异步操作都成功才成功
核心场景:处理并行的多个异步操作,且所有操作都必须成功,才返回所有结果;只要有一个操作失败,立即返回该失败原因(快速失败机制)。
核心特性
- 参数
iterable:可迭代对象(如数组),每个元素都是 Promise 实例;
- 成功结果:当所有 Promise 都变为
fulfilled,新 Promise 以 fulfilled 状态返回结果数组,数组顺序与传入的 Promise 顺序一致(与执行完成顺序无关);
- 失败机制:只要有一个 Promise 变为
rejected,新 Promise 立即以 rejected 状态返回该失败原因,其余未完成的 Promise 仍会执行,但结果会被忽略;
- 空数组:若传入空数组,会立即成功,返回空数组。
使用示例(并行请求多个接口)
// 模拟3个并行的接口请求
const request1 = Promise.resolve('接口1数据');
const request2 = new Promise(resolve => setTimeout(() => resolve('接口2数据'), 1000));
const request3 = Promise.resolve('接口3数据');
// 所有请求都成功才返回结果
Promise.all([request1, request2, request3])
.then(res => {
console.log('所有请求成功:', res); // 输出:['接口1数据', '接口2数据', '接口3数据'](1秒后)
const [data1, data2, data3] = res; // 按顺序解构
})
.catch(err => console.log('有请求失败:', err.message));
// 一个请求失败的情况
const request4 = Promise.reject(new Error('接口4请求失败'));
Promise.all([request1, request4, request3])
.then(res => console.log(res)) // 不会执行
.catch(err => console.log(err.message)); // 立即输出:接口4请求失败
4. Promise.race(iterable):第一个完成的异步操作决定结果
核心场景:处理并行的多个异步操作,谁先完成(成功/失败),就取谁的结果,其余未完成的 Promise 仍会执行,但结果会被忽略(“赛跑”机制)。
核心特性
- 参数
iterable:可迭代对象,每个元素都是 Promise 实例;
- 结果由“第一个定型”的 Promise 决定:无论第一个完成的是
fulfilled 还是 rejected,新 Promise 都会继承其状态和结果;
- 空数组:若传入空数组,新 Promise 会一直处于
pending 状态,永不定型。
经典使用示例:接口请求超时控制
// 模拟接口请求
function request() {
return new Promise((resolve) => {
setTimeout(() => resolve('接口请求成功'), 2000); // 2秒后完成
});
}
// 模拟超时器(1.5秒后失败)
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('请求超时:1.5秒未响应')), 1500);
});
}
// 赛跑:请求和超时器,谁先完成取谁的结果
Promise.race([request(), timeout()])
.then(res => console.log(res)) // 不会执行,因为超时器先完成
.catch(err => console.log(err.message)); // 输出:请求超时:1.5秒未响应
5. Promise.allSettled(iterable):等待所有异步操作都完成
核心场景:处理并行的多个异步操作,无论成功还是失败,都会等待所有操作完成,并返回每个操作的详细结果(包含状态和值/原因),解决了 Promise.all 快速失败的问题(适合需要知道所有操作结果的场景,如批量上传)。
核心特性
- ES2020 引入,现代浏览器均支持;
- 参数
iterable:可迭代对象,每个元素都是 Promise 实例;
- 成功结果:当所有 Promise 都定型(settled),新 Promise 一定是
fulfilled 状态,返回结果数组,数组顺序与传入顺序一致;
- 每个结果对象包含两个属性:
-
status:字符串,值为 fulfilled 或 rejected;
-
value:仅 status 为 fulfilled 时存在,是成功结果;
-
reason:仅 status 为 rejected 时存在,是失败原因。
使用示例(批量上传,需知道每个文件的上传结果)
// 模拟3个文件上传的异步操作(2成功1失败)
const upload1 = Promise.resolve('文件1上传成功');
const upload2 = Promise.reject(new Error('文件2上传失败:文件过大'));
const upload3 = Promise.resolve('文件3上传成功');
// 等待所有上传操作完成,获取每个操作的结果
Promise.allSettled([upload1, upload2, upload3])
.then(results => {
console.log('所有上传结果:', results);
// 筛选成功/失败的结果
const successList = results.filter(item => item.status === 'fulfilled').map(item => item.value);
const failList = results.filter(item => item.status === 'rejected').map(item => item.reason.message);
console.log('成功的上传:', successList); // ['文件1上传成功', '文件3上传成功']
console.log('失败的上传:', failList); // ['文件2上传失败:文件过大']
});
6. Promise.any(iterable):第一个成功的异步操作决定结果
核心场景:处理并行的多个异步操作,忽略失败的操作,等待第一个成功的操作返回结果;若所有操作都失败,才返回聚合错误(适合多节点请求,取最快成功的节点数据)。
核心特性
- ES2021 引入,现代浏览器均支持;
- 参数
iterable:可迭代对象,每个元素都是 Promise 实例;
- 成功机制:只要有一个 Promise 变为
fulfilled,新 Promise 立即继承其成功结果,其余未完成的 Promise 仍会执行,结果被忽略;
- 失败机制:若所有 Promise 都变为
rejected,新 Promise 会变为 rejected,抛出 AggregateError 错误(包含所有失败原因)。
使用示例(多节点请求,取最快成功的结果)
// 模拟3个节点的接口请求(2失败1成功,成功的节点最慢)
const requestNode1 = Promise.reject(new Error('节点1请求失败'));
const requestNode2 = Promise.reject(new Error('节点2请求失败'));
const requestNode3 = new Promise(resolve => setTimeout(() => resolve('节点3请求成功,获取数据'), 1000));
// 取第一个成功的请求结果
Promise.any([requestNode1, requestNode2, requestNode3])
.then(res => console.log('最快成功的节点:', res)) // 输出:节点3请求成功,获取数据
.catch(err => {
console.log('所有节点都失败:', err);
console.log('所有失败原因:', err.errors.map(e => e.message));
});
// 所有操作都失败的情况
Promise.any([Promise.reject('失败1'), Promise.reject('失败2')])
.catch(err => {
console.log(err instanceof AggregateError); // true
console.log(err.errors); // ['失败1', '失败2']
});
⌛ 五、Promise 经典实战场景
1. 封装 AJAX 请求(Promise 版)
传统 AJAX 基于回调,封装为 Promise 后支持链式调用和统一错误处理,是前端最基础的实战场景:
/**
* Promise 版 AJAX 封装
* @param {string} url - 请求地址
* @param {string} method - 请求方法:GET/POST
* @param {Object} data - 请求参数
* @returns {Promise}
*/
function ajax({ url, method = 'GET', data = {} }) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 处理GET参数
if (method.toUpperCase() === 'GET' && Object.keys(data).length > 0) {
const params = new URLSearchParams(data).toString();
url += '?' + params;
}
xhr.open(method, url, true);
// 设置POST请求头
if (method.toUpperCase() === 'POST') {
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
}
// 响应处理
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
try {
const res = JSON.parse(xhr.responseText); // 解析JSON
resolve(res);
} catch (err) {
reject(new Error('响应数据解析失败:' + err.message));
}
} else {
reject(new Error(`请求失败:状态码${xhr.status},${xhr.statusText}`));
}
};
// 网络错误
xhr.onerror = function() {
reject(new Error('网络错误:无法连接到服务器'));
};
// 发送请求
if (method.toUpperCase() === 'POST') {
xhr.send(new URLSearchParams(data).toString());
} else {
xhr.send();
}
});
}
// 使用示例
ajax({
url: '/api/user',
method: 'GET',
data: { id: 1 }
})
.then(res => console.log('请求成功:', res))
.catch(err => console.log('请求失败:', err.message));
2. 封装定时器(Promise 版)
将 setTimeout/setInterval 封装为 Promise,支持 async/await 调用,解决定时器嵌套问题:
/**
* Promise 版定时器(延迟执行)
* @param {number} time - 延迟时间(毫秒)
* @param {*} value - 延迟后返回的值
* @returns {Promise}
*/
function delay(time, value) {
return new Promise(resolve => {
setTimeout(() => resolve(value), time);
});
}
// 使用示例:async/await 调用
async function doTask() {
console.log('开始执行任务');
await delay(1000); // 延迟1秒
console.log('1秒后执行下一步');
const data = await delay(1000, '延迟2秒的返回值');
console.log(data); // 延迟2秒的返回值
}
doTask();
3. 异步流程控制:串行/并行执行
3.1 串行执行(依次执行,后一个依赖前一个结果)
适合有顺序依赖的异步操作(如先获取token,再用token获取用户信息):
// 模拟两个有依赖的异步操作
function getToken() {
return new Promise(resolve => setTimeout(() => resolve('user_token_123'), 1000));
}
function getUserInfo(token) {
return new Promise(resolve => setTimeout(() => resolve({ token, name: '张三', age: 18 }), 1000));
}
// 串行执行:async/await 更简洁(基于Promise)
async function getUserData() {
const token = await getToken(); // 先获取token
const userInfo = await getUserInfo(token); // 再用token获取用户信息
console.log('用户数据:', userInfo);
}
getUserData(); // 总耗时2秒
3.2 并行执行(同时执行,无依赖)
适合无依赖的异步操作,提升执行效率(如同时请求多个无关联的接口):
// 模拟3个无依赖的接口请求
function getArticleList() { return delay(1000, '文章列表'); }
function getCommentList() { return delay(1000, '评论列表'); }
function getLikeList() { return delay(1000, '点赞列表'); }
// 并行执行:Promise.all 实现,总耗时1秒(而非3秒)
async function getHomeData() {
console.time('总耗时');
// 同时执行,等待所有完成
const [articles, comments, likes] = await Promise.all([
getArticleList(),
getCommentList(),
getLikeList()
]);
console.log('首页数据:', { articles, comments, likes });
console.timeEnd('总耗时'); // 总耗时:约1000ms
}
getHomeData();
⚠️ 六、Promise 常见坑与避坑指南
1. 忘记写 return 导致链式调用断链
问题:.then() 中未写 return,导致下一个 .then() 接收的参数是 undefined,且执行时机提前(因为返回的是默认的 Promise.resolve(undefined))。
// 错误示例
Promise.resolve(1)
.then(num => {
console.log(num); // 1
// 忘记return,默认返回undefined
})
.then(num => console.log(num)); // undefined(立即执行)
// 正确示例
Promise.resolve(1)
.then(num => {
console.log(num); // 1
return num + 1; // 必须写return
})
.then(num => console.log(num)); // 2
2. 错误未被捕获(控制台报 UnhandledPromiseRejection)
问题:Promise 被 reject 后,没有任何 .catch() 或 .then() 的 onRejected 处理,导致浏览器抛出未捕获错误。
// 错误示例:无错误处理
Promise.reject(new Error('未捕获的错误')); // 控制台报错:UnhandledPromiseRejection
// 正确示例:必须添加catch
Promise.reject(new Error('已捕获的错误'))
.catch(err => console.log(err.message)); // 正常输出
3. 认为 Promise 执行器是异步的
问题:Promise 构造函数的执行器函数是同步立即执行的,内部的异步操作才是异步的,容易导致变量赋值顺序错误。
// 错误认知:认为执行器是异步的
let a = 0;
new Promise((resolve) => {
a = 1; // 同步执行,立即赋值
resolve();
});
console.log(a); // 1(而非0)
// 正确理解:执行器同步,内部操作异步
let b = 0;
new Promise((resolve) => {
setTimeout(() => {
b = 1; // 异步执行,1秒后赋值
resolve();
}, 1000);
});
console.log(b); // 0(立即输出)
4. Promise.all 传入非 Promise 数组
问题:Promise.all 传入的数组中包含普通值,会被自动转为 Promise.resolve(普通值),看似没问题,但如果包含同步错误,会立即触发失败。
// 无害情况:普通值被转为成功的Promise
Promise.all([1, 2, Promise.resolve(3)])
.then(res => console.log(res)); // [1,2,3]
// 危险情况:同步错误立即触发失败
Promise.all([1, 2, JSON.parse('{')]) // JSON.parse同步报错
.then(res => console.log(res))
.catch(err => console.log(err.message)); // Unexpected token } in JSON at position 1
避坑:确保 Promise.all 数组中的元素要么是 Promise 实例,要么是安全的普通值(无同步错误)。
5. 多次调用 resolve/reject 无效
问题:认为多次调用 resolve/reject 可以改变 Promise 状态,实则状态一旦定型,后续调用均无效。
new Promise((resolve, reject) => {
resolve('第一次resolve');
reject('reject'); // 无效
resolve('第二次resolve'); // 无效
})
.then(res => console.log(res)) // 第一次resolve
.catch(err => console.log(err)); // 不会执行
⏫ 七、Promise 高级知识点:微任务与事件循环
Promise 的回调函数(.then()/.catch()/.finally())属于微任务(Microtask),这是 Promise 执行时机的关键,也是前端面试的高频考点。
1. 微任务与宏任务的区别
浏览器的事件循环中,任务分为宏任务(Macrotask) 和微任务(Microtask),执行顺序为:先执行同步代码 → 执行所有微任务 → 执行一个宏任务 → 再执行所有微任务 → 循环。
-
宏任务:setTimeout、setInterval、AJAX、DOM 事件、script 整体代码;
-
微任务:Promise 回调、async/await、MutationObserver、queueMicrotask。
2. Promise 微任务执行示例
console.log('1. 同步代码开始'); // 同步
setTimeout(() => {
console.log('4. 宏任务:setTimeout'); // 宏任务,最后执行
}, 0);
new Promise((resolve) => {
console.log('2. Promise执行器:同步'); // 同步
resolve();
}).then(() => {
console.log('3. 微任务:Promise.then'); // 微任务,同步后执行
});
console.log('5. 同步代码结束'); // 同步
输出顺序:1→2→5→3→4(核心:微任务在宏任务前执行)。
📕 八、Promise 与 async/await 结合使用
async/await 是 ES7 引入的Promise 语法糖,它让异步代码的写法完全同步化,是目前前端异步编程的最优方案,但它的底层依然是 Promise,必须掌握 Promise 才能真正理解 async/await。
1. 核心规则
-
async 修饰的函数,返回值一定是 Promise 实例(即使返回普通值,也会被转为 Promise.resolve(普通值));
-
await 只能在 async 函数中使用,用于等待 Promise 定型,暂停函数执行,直到 Promise 返回结果;
-
await 后面可以跟任意值,若不是 Promise,会被自动转为 Promise.resolve(值);
- 错误处理:使用
try/catch 捕获 await 后的 Promise 错误(等价于 Promise 的 .catch())。
2. 结合使用示例(最简洁的异步代码)
// 模拟异步操作
const fetchData = () => delay(1000, '获取到数据');
const fetchUser = () => delay(1000, { name: '张三' });
// async/await 写法,同步化流程
async function main() {
try {
console.log('开始执行');
const data = await fetchData(); // 等待第一个异步操作
console.log(data); // 获取到数据
const user = await fetchUser(); // 等待第二个异步操作
console.log(user); // { name: '张三' }
console.log('所有操作完成');
} catch (err) {
console.log('错误:', err.message); // 捕获所有异步错误
} finally {
console.log('收尾操作'); // 无论成功/失败都执行
}
}
main();
📌 九、总结
- Promise 是 ES6 核心的异步编程解决方案,解决了回调地狱问题,提供了统一的异步接口和集中的错误处理机制;
- Promise 有三种不可逆状态:pending、fulfilled、rejected,仅能通过 resolve/reject 完成两次状态转换,状态定型后不可修改;
- 实例方法
.then()/.catch()/.finally() 支持链式调用,核心原因是每个方法都返回新的 Promise 实例,.catch() 统一捕获错误,.finally() 处理通用收尾;
- 构造函数静态方法是异步流程控制的核心:
all(所有成功)、race(第一个完成)、allSettled(所有完成)、any(第一个成功)、resolve/reject(快速创建);
- Promise 回调属于微任务,执行顺序在同步代码后、宏任务前,这是理解 Promise 执行时机的关键;
- async/await 是 Promise 的语法糖,让异步代码同步化,是目前最优的异步写法,但底层依赖 Promise;
- 开发中避免常见坑:忘记 return 断链、未捕获错误、混淆执行器同步特性、多次调用 resolve/reject;
- Promise 是现代前端开发的必备技能,是 Vue、React 等框架异步操作、接口请求、状态管理的基础,也是前端面试的高频考点。
掌握 Promise 不仅能写出更优雅、更易维护的异步代码,更能理解现代前端异步编程的底层逻辑,为后续学习 async/await、事件循环、前端工程化打下坚实的基础。