把 Promise 想成"奶茶店取号单":你下单后拿到一张纸条(promise 对象),单子上写着"正在做";做好了叫号,你走 .then 取餐;做砸了走 .catch 退款。它把"异步的事"包起来,成功走 then、失败走 catch,而且状态一旦定下来就不能再改。
以前异步操作像"传话游戏"——一层套一层,套深了就叫回调地狱。.then 改成"排队叫号",一步接一步,平着写。
// 以前:一层套一层,套到头晕(这就是"回调地狱")
setTimeout(function () { // 第 1 个定时器
console.log("第一步"); // 先做第一步
setTimeout(function () { // 等第一步完了,再套一层定时器
console.log("第二步");
setTimeout(function () { console.log("第三步"); }, 300); // 再套一层,深到缩进都歪了
}, 300);
}, 300);
// 现在:then 排队,一行一步(平着写,不再缩进套娃)
new Promise((resolve) => { // 造一个新承诺,resolve 是"成功时叫谁"
setTimeout(() => { // 等 300 毫秒
console.log("第一步"); // 先做第一步
resolve(); // 做完了,喊 resolve 通知下一步
}, 300);
})
.then(() => { console.log("第二步"); }) // 第一步做完叫第二步
.then(() => { console.log("第三步"); }); // 第二步做完叫第三步
一张取号单只会经历三种状态,而且只能"从等待中走一次"。
pending—— 等待中( 刚下单, 奶茶还在做) // 刚拿到取号单,还没结果 fulfilled—— 成功( resolve 触发, 做好了) // 做成了,resolve 一叫就进这个状态 rejected—— 失败( reject 触发, 做砸了) // 做砸了,reject 一叫就进这个状态
像外卖订单:刚下单 pending,做好了 fulfilled,做砸了 rejected。状态只能从 pending 变一次,定了就改不了——你不能取了餐又退成"还在做"。
.then 收成功的结果,.catch 收失败的原因,各管各的。
let p = new Promise((resolve, reject) => { // 造承诺:resolve 管成功,reject 管失败
setTimeout(() => { // 等 300 毫秒后判断成败
let ok = false; // 假设这次是失败
if (ok) resolve("数据"); // 成功:调 resolve 并把结果传出去
else reject("出错了"); // 失败:调 reject 并把原因传出去
}, 300);
});
p.then(data => console.log("成功:", data)) // 成功走这里,拿到 data
.catch(err => console.log("失败:", err)); // 失败走这里,拿到 err
如果你写了 .then 却没写 .catch,又碰上 reject,控制台会冒出一条红色的"Uncaught (in promise)"警告。这就是异步失败没人接的信号——任何可能失败的异步操作,后面都该跟一个 .catch。
实战里最常见的套路是:把"等一件事"包成 Promise,外面用 then 接。
function delay(ms) { // 把"等 ms 毫秒"包成一个承诺
return new Promise(resolve => { // 返回一个新承诺
setTimeout(resolve, ms); // ms 毫秒后自动成功(resolve 直接当定时器回调)
});
}
delay(1000).then(() => console.log("1 秒后执行")); // 等 1 秒,再打印这句
把"等 1 秒"包成 Promise,外面用 .then 接。真实项目就是用这个思路包 Ajax、读文件、加载图片——比如 fetch 本身返回的就是 Promise。
let p = new Promise((resolve, reject) => {
resolve("第一次"); // 先成功,状态变成 fulfilled
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 浪费时间。