Promise 就是异步操作的容器,解决回调地狱,支持链式调用。
核心三个状态:
① pending——进行中
② fulfilled——成功,then
③ rejected——失败,catch
④ all/race——全部完成/最快完成
坑:状态不可逆、then 返回新 Promise、catch 也是 then、Promise.all 一个失败全失败。
以前异步操作像"传话游戏"——一层套一层,套深了就叫回调地狱。.then 改成"排队叫号",一步接一步,平着写。
// 以前:一层套一层,套到头晕(这就是"回调地狱")
// 第 1 个定时器
setTimeout(function () {
// 先做第一步
console.log("第一步");
// 等第一步完了,再套一层定时器
setTimeout(function () {
console.log("第二步");
// 再套一层,深到缩进都歪了
setTimeout(function () { console.log("第三步"); }, 300);
}, 300);
// 现在:then 排队,一行一步(平着写,不再缩进套娃)
}, 300);
// 造一个新承诺,resolve 是"成功时叫谁"
new Promise((resolve) => {
// 等 300 毫秒
setTimeout(() => {
// 先做第一步
console.log("第一步");
// 做完了,喊 resolve 通知下一步
resolve();
}, 300);
})
// 第一步做完叫第二步
.then(() => { console.log("第二步"); })
// 第二步做完叫第三步
.then(() => { console.log("第三步"); });
一张取号单只会经历三种状态,而且只能"从等待中走一次"。
// 刚拿到取号单,还没结果 pending—— 等待中( 刚下单, 奶茶还在做) // 做成了,resolve 一叫就进这个状态 fulfilled—— 成功( resolve 触发, 做好了) // 做砸了,reject 一叫就进这个状态 rejected—— 失败( reject 触发, 做砸了)
像外卖订单:刚下单 pending,做好了 fulfilled,做砸了 rejected。状态只能从 pending 变一次,定了就改不了——你不能取了餐又退成"还在做"。
.then 收成功的结果,.catch 收失败的原因,各管各的。
// 造承诺:resolve 管成功,reject 管失败
let p = new Promise((resolve, reject) => {
// 等 300 毫秒后判断成败
setTimeout(() => {
// 假设这次是失败
let ok = false;
// 成功:调 resolve 并把结果传出去
if (ok) resolve("数据");
// 失败:调 reject 并把原因传出去
else reject("出错了");
}, 300);
});
// 成功走这里,拿到 data
p.then(data => console.log("成功:", data))
// 失败走这里,拿到 err
.catch(err => console.log("失败:", err));
如果你写了 .then 却没写 .catch,又碰上 reject,控制台会冒出一条红色的"Uncaught (in promise)"警告。这就是异步失败没人接的信号——任何可能失败的异步操作,后面都该跟一个 .catch。
实战里最常见的套路是:把"等一件事"包成 Promise,外面用 then 接。
// 把"等 ms 毫秒"包成一个承诺
function delay(ms) {
// 返回一个新承诺
return new Promise(resolve => {
// ms 毫秒后自动成功(resolve 直接当定时器回调)
setTimeout(resolve, ms);
});
}
// 等 1 秒,再打印这句
delay(1000).then(() => console.log("1 秒后执行"));
把"等 1 秒"包成 Promise,外面用 .then 接。真实项目就是用这个思路包 Ajax、读文件、加载图片——比如 fetch 本身返回的就是 Promise。
let p = new Promise((resolve, reject) => {
// 先成功,状态变成 fulfilled
resolve("第一次");
// 没用,状态已经是成功了,后面这句直接被忽略
reject("第二次");
});
// 第一次
p.then(v => console.log(v));
如果你在同一个 Promise 里先调了 resolve 又调 reject,后面那个会被直接忽略。因为取号单一旦"叫过号"就作废,不能反悔——这也是 Promise 比回调靠谱的地方。
① 可能在什么地方用:封装 fetch/ajax 请求、读取文件/图片、setTimeout 包装成可 await 的延时、多步异步任务排队(登录→拉用户→拉订单)。
② 常见的问题:漏写 .catch 导致静默报错;.then 里忘记 return,下一个 then 拿到 undefined;把同步代码误包进 Promise 以为在异步。
③ 解决思路:每个 Promise 链末尾都加 .catch;.then 之间要传值就 return 数据;多个无依赖请求用 Promise.all([p1, p2]) 并发,别串行 await 浪费时间。
| API | 作用 | 参数 | 返回值 | 代码示例 |
|---|---|---|---|---|
| new Promise(fn) | 造一个异步任务的承诺单 | (resolve, reject) => {} | 一个 Promise | new Promise(r => r(1)); |
| .then(cb) | 成功之后接结果 | 成功回调 | 返回新 Promise | p.then(v => console.log(v)); |
| .catch(cb) | 失败之后接原因 | 失败回调 | 返回新 Promise | p.catch(e => console.log(e)); |
| resolve(x) | 宣布成功并把结果传出去 | 成功的值 | 成功状态 | resolve("ok"); |