← 返回目录

9. Promise

Promise 就是异步操作的容器,解决回调地狱,支持链式调用。

核心三个状态:
pending——进行中
fulfilled——成功,then
rejected——失败,catch
all/race——全部完成/最快完成

坑:状态不可逆、then 返回新 Promise、catch 也是 then、Promise.all 一个失败全失败

9.1 互动演示(外卖订单模拟)

点按钮,看 Promise 的 pending → fulfilled / rejected 流转。

9.2 知识点讲解

① 以前:回调套回调 vs 现在:then 排队

以前异步操作像"传话游戏"——一层套一层,套深了就叫回调地狱.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

.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 比回调靠谱的地方。

9.3 实战:用在哪 / 常见坑 / 怎么解决

① 可能在什么地方用:封装 fetch/ajax 请求、读取文件/图片、setTimeout 包装成可 await 的延时、多步异步任务排队(登录→拉用户→拉订单)。

② 常见的问题:漏写 .catch 导致静默报错;.then 里忘记 return,下一个 then 拿到 undefined;把同步代码误包进 Promise 以为在异步。

③ 解决思路:每个 Promise 链末尾都加 .catch.then 之间要传值就 return 数据;多个无依赖请求用 Promise.all([p1, p2]) 并发,别串行 await 浪费时间。

一句话:Promise = 把"成功怎么办"和"失败怎么办"分开写的异步工具,状态定一次就改不了。

本页重点

API作用参数返回值代码示例
new Promise(fn)造一个异步任务的承诺单(resolve, reject) => {}一个 Promisenew Promise(r => r(1));
.then(cb)成功之后接结果成功回调返回新 Promisep.then(v => console.log(v));
.catch(cb)失败之后接原因失败回调返回新 Promisep.catch(e => console.log(e));
resolve(x)宣布成功并把结果传出去成功的值成功状态resolve("ok");