Skip to content

6. 说说你对 Promise 的理解 ​

难度等级:🔴 高级 分类:JavaScript 归档日期:2026-10-27


一、为什么需要 Promise ​

在没有 Promise 之前,JS 的异步任务采用的是回调函数的方式,这就会导致回调嵌套,代码难以维护。

Promise 就是为解决这个问题应运而生的。其实 Promise 也没有消除回调函数,then、catch 中传入的依然是回调,函数作为参数也是 JS 的重要特性之一。它改善的是代码的组织方式,并且能够集中处理错误。

二、三种状态 ​

Promise 的实例表示某个任务(操作)的状态,它只有三种状态:等待 pending、已兑现 fulfilled、已拒绝 rejected。一旦变为 fulfilled 或 rejected(统称 settled),就不能再改变。

三、执行器 ​

new Promise 接收一个执行器函数作为参数,创建 Promise 对象时执行器会立即同步执行。它接收两个参数 resolve、reject,都是 Promise 内部提供的函数。

  • 执行器 return 的值会被忽略,Promise 的状态和结果由 resolve 或 reject 的调用决定;
  • 执行器同步执行过程中抛出异常,也会使 Promise 被拒绝;但执行器中异步执行抛出的异常不会使 Promise 变为 rejected,状态仍是 pending;
  • resolve 和 reject 不会结束执行器代码的执行,return 会;
  • 常见写法 return resolve(123) 是有效的,相当于 resolve(123); return;(resolve 的返回值是 undefined)。
javascript
new Promise(resolve => {
  return resolve(123); // return 的 undefined 会被忽略
});

四、then、catch、finally ​

执行器函数本身是同步执行的,.then 注册的回调才是异步的。(可以参考 thenable 的例子)

可以用 .then() 获取结果,它接收两个参数:p.then(onFulfilled, onRejected)。

同一个 Promise 可以注册多次处理:

javascript
const p = Promise.resolve(100);

p.then(value => console.log('A', value));
p.then(value => console.log('B', value));

1. 返回值规则 ​

方法回调正常返回普通值回调抛出异常回调返回 Promise
.then()新 Promise 以返回值兑现新 Promise 以该异常为原因被拒绝新 Promise 接纳返回的 Promise 的最终状态和结果
.catch()新 Promise 以返回值兑现,从失败中恢复新 Promise 以该异常为原因被拒绝新 Promise 接纳返回的 Promise 的最终状态和结果
.finally()忽略返回值,沿用之前的状态和结果新 Promise 以该异常为原因被拒绝等待返回的 Promise:成功则沿用之前的状态和结果;失败则以新的拒绝原因为准

.then()、.catch():返回的 Promise 最终怎样,新 Promise 就怎样。

javascript
Promise.resolve(1)
  .then(() => {
    return Promise.resolve(100); // 这个也会走两步微任务
  })
  .then(value => {
    console.log(value); // 100
  });

// 上面的例子想到:p1 会同步返回且 p === p1,p2 会走两步微任务且 p 不等于 p2
const p = Promise.resolve(100);
const p1 = Promise.resolve(p);
const p2 = new Promise(resolve => {
  resolve(p);
});

// -------

Promise.reject('原来的错误')
  .catch(() => {
    return Promise.reject('新的错误');
  })
  .catch(reason => {
    console.log(reason); // '新的错误'
  });

.finally():返回的 Promise 成功时,不会替换之前的结果;失败时才会覆盖。

javascript
Promise.resolve(1)
  .finally(() => {
    return Promise.resolve(100);
  })
  .then(value => {
    console.log(value); // 1,不是 100
  });

Promise.resolve(1)
  .finally(() => {
    return Promise.reject('清理失败');
  })
  .catch(reason => {
    console.log(reason); // '清理失败'
  });

补充两点:

  • 不写 return,相当于返回普通值 undefined;
  • 严格来说,除了 Promise,返回 thenable(具有可调用 then 方法的对象) 也需要按类似规则处理。

表格中的规则都以对应回调实际执行了为前提。

2. 回调参数 ​

方法回调接收的参数
.then(onFulfilled, onRejected)成功回调接收兑现的值 value;失败回调接收拒绝原因 reason
.catch(onRejected)接收拒绝原因 reason
.finally(onFinally)不接收参数

3. 传入非函数会被忽略 ​

传入的参数不是函数时会被直接跳过,值原样传给下一个 then。不传参数的 .then() 相当于 .then(x => x)。

javascript
Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then(console.log); // 1

Promise.resolve(333)
  .then(111, 222)
  .then(console.log); // 333

4. 回调返回普通值、thenable、Promise 的区别 ​

thenable 就是带有可调用的 then 方法的对象。下面都以 .then() 回调的返回值为例。

(1)返回普通值 ​

javascript
const p = Promise.resolve().then(() => {
  return 100;
});

p.then(value => {
  console.log(value); // 100
});

回调返回 100,.then() 创建的新 Promise 就以 100 兑现。

(2)返回自定义的 thenable 对象 ​

javascript
const myThenable = {
  then(resolve, reject) {
    resolve(200);
  }
};

const p = Promise.resolve().then(() => {
  return myThenable;
});

p.then(value => {
  console.log(value); // 200
});

这里不会直接把 myThenable 当作最终结果,而是异步调用它的 then 方法,根据它调用 resolve 或 reject 的情况,确定新 Promise 的结果。

(3)返回原生 Promise 对象 ​

javascript
const p = Promise.resolve().then(() => {
  return Promise.resolve(300);
});

p.then(value => {
  console.log(value); // 300
});

.then() 创建的新 Promise 会跟随返回的 Promise 的状态和结果:这里返回的 Promise 以 300 兑现,所以 p 最终也以 300 兑现。

注意:返回另一个 Promise,不等于直接把那个 Promise 作为 .then() 的返回值。.then() 返回的仍然是一个新 Promise。

javascript
const p = Promise.resolve(100);
const p2 = Promise.resolve(1)
  .then(() => {
    return p; // 这个也会走两步微任务
  });

五、静态方法 ​

  • Promise.all:全部成功才成功,一个失败就失败;
  • Promise.allSettled:等所有项都有结果,汇总成功和失败(会返回一个包含状态和值的列表);
  • Promise.race:谁先出结果就跟谁,无论成功还是失败;
  • Promise.any:谁先成功就用谁,全部失败才失败。

六、要点补充 ​

  1. 注册(登记)回调,不等于立刻把对应任务放入微任务队列。 在一个 pending 状态的 Promise 上调用 then,只是把回调登记在这个 Promise 上,等它 settled 之后回调才会加入微任务队列。

  2. 补充一个额外话题 Settimeout(()=>null, 3000),执行代码时也只是登记,真正加入任务队列是在条件触发(此处是3s 之后)的时候。用户事件也是同理。和微任务一样,加入队列执行的是回调函数。 另外:3000ms 是最短等待时间,不保证回调准时执行

  3. 回调返回一个 Promise 时,会慢两拍,“多两个 tick”可以这样理解。

    当 then(() => { return p }) 或 new Promise(resolve => resolve(p)) 的时候:

    • 第一步: 发现 p 是个对象,就读取它的 then 属性(仅读取不调用,会触发 getter),发现 then 是个方法,那 JS 引擎就要小心了,因为 then 里面可能报错,可能有异步任务,新返回 promise 对象需要依赖它们的状态,不能里面的没完成或者报错了,外面的就直接 settled 了。接下来会包装一个称为 NewPromiseResolveThenableJob 的微任务加入微任务队列,这是第一步;
    • 第二步: 当执行到 NewPromiseResolveThenableJob 这个微任务时,此时会调用 then 方法。如果 then 是自己实现的且是同步的执行,直接把状态传出去,外层 Promise 兑现;如果 then 是原生的 Promise 的方法,也会调用执行 then,但是按照规范要求,then 的回调函数必须异步执行,此时把回调加入微任务队列,这个回调一般称为 reactionJob,这时外部 promise 的状态依然 pending;
    • 第三步: 自己写的 thenable 没有第三步了,原生的 then 方法回调在微任务里排队排到了,就执行它。

七、Promise 的内部结构 ​

把 Promise 想成一个很简单的状态机,它只对外提供两种能力:

  • 订阅(then):“等你有结果了,通知我。”
  • 定局(resolve / reject):“你的结果是这个。”

所以内部只有几样东西:

  • [[PromiseState]]:pending / fulfilled / rejected
  • [[PromiseResult]]:结果值 value
  • [[PromiseFulfillReactions]]:fulfilled 订阅者列表(谁等着我的通知呢)
  • [[PromiseRejectReactions]]:rejected 订阅者列表

重点是:外界(包括另一个 promise)没有任何办法能读取这些内部槽,想知道一个 promise 的结果,唯一的途径就是调用它的 then 订阅它。

八、需要注意的点和容易踩的坑 ​

  1. return Promise 的情况

    javascript
    const p = Promise.resolve(123);
    const p1 = Promise.resolve(p);
    p === p1; // true
    const p2 = new Promise(resolve => { resolve(p) }); // 慢两拍
    p === p2; // false
    
    const p3 = new Promise(resolve => { resolve(Promise.resolve(123)) }); // 这种情况一样的,Promise.resolve(123) 同步得出,但 return 的时候慢两拍
  2. thenable 的情况:发现是对象且有 then 方法的时候会触发 then 的 getter,然后才产生 NewPromiseResolveThenableJob 加入微任务队列。

    javascript
    const obj = {
      get then() {
        console.log('A');
    
        return resolve => {
          console.log('B');
          resolve(100);
          console.log('C');
        };
      }
    };
  3. await 的快速路径:这是比较新的规范层面的优化,以前 await 确实更慢,要三步,现在快速路径直接拿到 p,只是 await 续体走一个微任务。

    javascript
    const p = Promise.resolve(100);
    
    async function a() {
      await p; // 快速路径:直接复用 p,await 的续体还是要加入微任务队列的
      console.log('A');
    }
    
    async function b() {
      await new Promise(resolve => {
        resolve(p); // 新 Promise 需要接纳 p 的状态
      });
    
      console.log('B');
    }
    
    b();
    a();
    
    queueMicrotask(() => console.log('C'));
    
    console.log('D');
    javascript
    const p1 = async function a() {
      await p; // 快速路径,虽然后面没代码了,但还是要走 await 续体的,就是说还是要走一个微任务,p1 才能兑现结果
    };
  4. 要知道登记的概念

    javascript
    Promise.resolve()
      .then(() => console.log('A'))
      .then(() => console.log('B'))
      .then(() => console.log('C'));
  5. await 先求值右侧,再暂停

    javascript
    await new Promise(resolve => {
      console.log('A');
      resolve(100);
      console.log('B');
    });

    顺序是:

    1. 同步执行 Promise 执行器。
    2. 输出 A,resolve,再输出 B。
    3. new Promise(...) 返回。
    4. await 登记恢复逻辑;此时 Promise 已成功,安排续体微任务。

    如果 await 等待时 Promise 还是 pending,就先登记,等它敲定后才安排续体。

  6. 通常 resolve(p)、return p 慢两拍;resolve(thenable')、return p(thenable') 慢一拍,但下面这种情况 thenable 更慢

    javascript
    const thenable = { then(resolve) { ... } };
    const originP = Promise.resolve(p); // p 是一个已兑现的 Promise 对象

    originP 同步直接走完;thenable 要走一个 NewPromiseResolveThenableJob 的过程,就多了这一拍,后面的 reactionJob,thenable 不走。

个人面试题归档,持续更新中