Skip to content

Promise 对象

在 JavaScript 中,Promise 用来表示一个异步操作的最终结果。

按照 MDN 的定义,Promise 表示“一个异步操作最终会完成还是失败,以及它对应的结果值”。你可以把它理解成:先拿到一个“未来结果的占位对象”,等异步任务结束后,再统一处理成功值或失败原因

为什么要用 Promise

传统异步代码通常依赖回调函数。随着逻辑变复杂,回调会一层套一层,代码容易出现下面这些问题:

  • 嵌套过深,可读性差
  • 错误处理分散,不容易统一管理
  • 多个异步步骤串联时,代码维护成本高

Note

  • 支持链式调用,能明显缓解“回调地狱”
  • 成功和失败的处理方式更统一
  • 更容易组合多个异步任务

Promise 如何封装异步

最常见的写法是通过 new Promise(...) 创建一个 Promise 对象:

  • new Promise(executor) 会立即创建一个 Promise
  • executor 是执行器函数,它会同步执行
  • executor 接收两个参数:resolvereject
  • 调用 resolve(value) 表示操作成功
  • 调用 reject(reason) 表示操作失败

work flow

通常我们会在异步任务完成后调用 resolvereject,再通过 thencatchfinally 注册后续处理逻辑。

下面是一个简单的抽奖示例。这里使用 setTimeout 来模拟异步操作;它不会阻塞主线程,只是 1 秒后再执行回调。

function getRandomNumber(min, max) {
  return Math.ceil(Math.random() * (max - min + 1)) + min - 1;
}

const btn = document.querySelector("button");

btn.addEventListener("click", function () {
  const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      const num = getRandomNumber(1, 100);

      if (num <= 30) {
        resolve(num);
      } else {
        reject(num);
      }
    }, 1000);
  });

  promise.then(
    (num) => {
      console.log(`中奖了,号码是:${num}`);
    },
    (num) => {
      console.log(`没中奖,再试一次:${num}`);
    }
  );
});

执行顺序

有一个很容易混淆的点:

  • Promise 的执行器 executor同步执行
  • thencatchfinally 注册的回调不会立刻执行
  • 这些回调会在当前同步代码执行结束后再执行

示例:

const promise = new Promise((resolve) => {
  console.log("Promise");
  resolve();
});

promise.then(() => {
  console.log("resolved");
});

console.log("Hi!");

输出顺序:

Promise
Hi!
resolved

原因是:^^执行器先同步运行,而 then 的回调会等当前调用栈清空后再执行.^^

Promise 的状态与结果

三种状态

Promise 只有三种状态:

  • pending:等待中
  • fulfilled:已成功
  • rejected:已失败

状态变化只有两种:

  • pending -> fulfilled
  • pending -> rejected

并且一旦状态确定,就不会再改变

结果值

除了状态,Promise 还会保存本次异步操作的结果:

  • 成功时保存 value
  • 失败时保存 reason

后续的回调函数拿到的,就是这个结果值。

Note

有些资料会把 fulfilled 写成 resolved,但严格来说,resolved 不是 Promise 的正式状态名

  • 状态名只有:pendingfulfilledrejected
  • settled 表示“已经落定”,也就是 fulfilledrejected
  • resolved 更准确地说是“已经被处理/跟随某个结果”,它不一定等于 fulfilled

例如,一个 Promise 可以“resolved 到另一个 Promise”,但它自己此时仍然可能暂时处于 pending

const inner = new Promise((resolve) => {
  setTimeout(() => resolve("ok"), 1000);
});

const outer = Promise.resolve(inner);

outer.then((value) => {
  console.log(value);
});

上面的 outer 会跟随 inner 的最终状态;在 inner 完成之前,outer 也可能还是 pending

Promise 常用 API

构造函数

new Promise(executor)

executor 是执行器函数,签名如下:

new Promise((resolve, reject) => {
  // ...
});

要点:

  • executor 会在创建 Promise 时立即同步执行
  • resolve 用于将状态变为 fulfilled
  • reject 用于将状态变为 rejected
  • 如果 executor 内部抛出异常,Promise 会自动变为 rejected
  • executor 的返回值会被忽略
const promise = new Promise((resolve, reject) => {
  console.log("111");
});

console.log("222");

输出:

111
222

实例方法

then()

方法签名:promise.then(onFulfilled, onRejected)

说明:

  • 第一个参数在 Promise 变为 fulfilled 时调用
  • 第二个参数在 Promise 变为 rejected 时调用
  • 两个参数都是可选的
  • then() 会立即返回一个新的 Promise,因此可以继续链式调用

返回值规则是理解链式调用的关键:

  • 返回普通值:新的 Promise 会以这个值变为 fulfilled
  • 返回 Promise 或 thenable:新的 Promise 会跟随它的结果
  • 抛出错误:新的 Promise 会变为 rejected
Promise.resolve(1)
  .then((value) => value + 1)
  .then((value) => Promise.resolve(value + 1))
  .then((value) => {
    console.log(value);
  });

输出:

3

catch()

方法签名:promise.catch(onRejected)

catch() 用于指定失败时的回调,本质上可以理解为:

promise.then(undefined, onRejected)

示例:

const promise = new Promise((resolve, reject) => {
  reject("error");
});

promise.catch((error) => {
  console.log(error);
});

finally()

方法签名:promise.finally(onFinally)

finally() 用于注册一个“无论成功还是失败都会执行”的回调。它常用于收尾逻辑,例如:

  • 关闭 loading
  • 清理资源
  • 记录日志

需要注意:

  • finally() 的回调不接收成功值或失败原因
  • 它也会返回一个新的 Promise
  • 默认情况下,它不会改变前一个 Promise 的结果
  • 但如果在 finally() 中抛出错误,或返回一个被拒绝的 Promise,链就会变为失败
Promise.resolve("ok")
  .finally(() => {
    console.log("cleanup");
  })
  .then((value) => {
    console.log(value);
  });

输出:

cleanup
ok

静态方法

Promise.resolve()

方法签名:Promise.resolve(value)

作用:把一个值转换为 Promise。

规则:

  • 如果 value 本身就是 Promise,通常会直接返回它
  • 如果 value 是 thenable,对应的 then 会被调用
  • 否则会返回一个以该值为结果的 fulfilled Promise
const promise = Promise.resolve("ok");

promise.then((value) => {
  console.log(value);
});

输出:

ok

如果传入的是 Promise,那么返回值会跟随它的状态:

const promise = Promise.resolve(
  new Promise((resolve) => {
    resolve("done");
  })
);

promise.then((value) => {
  console.log(value);
});

Promise.reject()

方法签名:Promise.reject(reason)

作用:返回一个被拒绝的 Promise。

要点:

  • 它总是返回一个 rejected Promise
  • 传入什么,就把什么作为拒绝原因
  • 即使传入的是一个 Promise,也不会展开它
const promise = Promise.reject("Hello");

promise.catch((reason) => {
  console.log(reason);
});

输出:

Hello

传入 Promise 时:

const promise = Promise.reject(
  new Promise((resolve) => {
    resolve("ok");
  })
);

promise.catch((reason) => {
  console.log(reason);
});

这里拿到的 reason 就是那个 Promise 对象本身,而不是它内部的成功值。

Promise.all()

方法签名:Promise.all(iterable)

作用:并发执行多个 Promise,并在全部成功时得到结果。

规则:

  • 所有输入都成功时,返回 fulfilled
  • 结果是一个数组,顺序与传入顺序一致
  • 只要有一个失败,就会立刻返回 rejected
  • 拒绝原因是第一个失败的 Promise 的原因
const p1 = Promise.resolve("Hello");
const p2 = Promise.resolve("Nice");
const p3 = Promise.resolve("Meet");

Promise.all([p1, p2, p3]).then((value) => {
  console.log(value);
});

输出:

[ 'Hello', 'Nice', 'Meet' ]

有任意一个失败时:

const p1 = Promise.resolve("Hello");
const p2 = Promise.reject("Nice");
const p3 = Promise.reject("Meet");

Promise.all([p1, p2, p3]).catch((error) => {
  console.log(error);
});

输出:

Nice

Promise.race()

方法签名:Promise.race(iterable)

作用:多个 Promise 中,谁先敲定结果,就采用谁的结果。

这里的“先”既可能是先成功,也可能是先失败。

const p1 = new Promise((resolve) => {
  setTimeout(() => resolve("Hello"), 100);
});

const p2 = new Promise((resolve, reject) => {
  setTimeout(() => reject("Nice"), 200);
});

Promise.race([p1, p2]).then(
  (value) => {
    console.log(value);
  },
  (error) => {
    console.log(error);
  }
);

输出:

Hello

Promise.allSettled()

方法签名:Promise.allSettled(iterable)

作用:等待所有 Promise 都有结果后,再统一返回每一项的状态。

特点:

  • 不要求全部成功
  • 一定会等到所有 Promise 都结束
  • 返回结果中会明确标记每一项是成功还是失败
const p1 = Promise.resolve("Hello");
const p2 = Promise.reject("Nice");
const p3 = Promise.reject("Meet");

Promise.allSettled([p1, p2, p3]).then((value) => {
  console.log(value);
});

输出:

[
  { status: 'fulfilled', value: 'Hello' },
  { status: 'rejected', reason: 'Nice' },
  { status: 'rejected', reason: 'Meet' }
]

Promise.any()

方法签名:Promise.any(iterable)

作用:只要有一个 Promise 成功,就返回这个最先成功的结果。

规则:

  • 只关心“第一个成功”
  • 如果全部失败,返回 rejected
  • 全部失败时,拒绝原因是一个 AggregateError
const p1 = Promise.resolve("Hello");
const p2 = Promise.reject("Nice");
const p3 = Promise.reject("Meet");

Promise.any([p1, p2, p3])
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error);
  });

输出:

Hello

如果全部失败:

const p1 = Promise.reject("Hello");
const p2 = Promise.reject("Nice");
const p3 = Promise.reject("Meet");

Promise.any([p1, p2, p3]).catch((error) => {
  console.log(error instanceof AggregateError);
  console.log(error.errors);
});

多个回调的情况

如果为同一个 Promise 绑定多个回调,那么这些回调都会执行。

也就是说,Promise 的结果一旦确定,后续再绑定上去的对应回调,也依然能够拿到这个结果。

const promise = Promise.resolve("ok");

promise.then((value) => {
  console.log("第一个回调:", value);
});

promise.then((value) => {
  console.log("第二个回调:", value);
});

状态变化和回调执行的先后

Promise 的状态先确定,对应回调才会执行。

可以这样理解:

  • 如果在同步代码中调用 resolve()reject(),状态会先变更,回调稍后执行
  • 如果在异步任务中调用 resolve()reject(),那就先等异步任务运行到那一刻,状态才发生变化,然后再执行回调

无论哪种情况,都可以记成一句话:先落定状态,再调度回调。