前端 怎么能不懂 Promise 呢?

510 阅读3分钟

目录

  1. 前言:为什么需要 Promise?
  2. Promise 的基础使用
  3. Promise 的运行机制:事件循环与微任务
  4. 手写 Promise 源码
  5. Promise 实际应用案例
  6. Promise 与 async/await 的关系
  7. 总结
  8. 参考资料

一、前言:为什么需要 Promise?

在 JavaScript 早期,异步任务主要通过 回调函数(callback)实现。但回调容易导致“回调地狱”:层层嵌套、难以维护。
Promise 的出现就是为了解决 异步流程的可读性和可维护性 问题。


二、Promise 的基础使用

1. 创建与状态

Promise 有三种状态:

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

一旦状态从 pending 变为 fulfilledrejected,就不能再改变。

promises.png

const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("成功结果");
    // 或者 reject("失败原因");
  }, 1000);
});

p.then(result => console.log(result))
 .catch(error => console.error(error))
 .finally(() => console.log("任务结束"));

2. then / catch / finally

  • then:接收成功结果
  • catch:接收失败原因
  • finally:无论成功失败都会执行
fetch("/api/data")
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => console.error(err))
  .finally(() => console.log("请求结束"));

3. Promise 的静态方法

  • Promise.resolve(value)
  • Promise.reject(error)
  • Promise.all([...]):全部成功才成功,有一个失败就失败
  • Promise.race([...]):谁先返回结果就采用谁
  • Promise.allSettled([...]):无论成功失败都收集结果
  • Promise.any([...]):只要有一个成功就返回
Promise.all([fetch("/a"), fetch("/b")])
  .then(([resA, resB]) => console.log(resA, resB));

三、Promise 的运行机制:事件循环与微任务

Promise 的回调(then/catch/finally)属于 微任务

执行顺序规则:

  1. 先执行同步代码。
  2. 再执行所有微任务(Promise 回调)。
  3. 最后执行下一个宏任务(如 setTimeout)。

例子:

console.log("start");

setTimeout(() => console.log("timeout"));

Promise.resolve().then(() => console.log("promise"));

console.log("end");

// 输出顺序: start → end → promise → timeout

四、手写 Promise 源码

1. 最小可用版

class MyPromise {
  constructor(executor) {
    this.state = "pending";
    this.value = undefined;
    this.reason = undefined;
    this.onFulfilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.state === "pending") {
        this.state = "fulfilled";
        this.value = value;
        this.onFulfilledCallbacks.forEach(fn => fn());
      }
    };

    const reject = (reason) => {
      if (this.state === "pending") {
        this.state = "rejected";
        this.reason = reason;
        this.onRejectedCallbacks.forEach(fn => fn());
      }
    };

    try {
      executor(resolve, reject);
    } catch (err) {
      reject(err);
    }
  }

  then(onFulfilled, onRejected) {
    if (this.state === "fulfilled") {
      onFulfilled(this.value);
    } else if (this.state === "rejected") {
      onRejected(this.reason);
    } else {
      this.onFulfilledCallbacks.push(() => onFulfilled(this.value));
      this.onRejectedCallbacks.push(() => onRejected(this.reason));
    }
  }
}

2. 支持链式调用(核心)

then(onFulfilled, onRejected) {
  return new MyPromise((resolve, reject) => {
    if (this.state === "fulfilled") {
      try {
        const result = onFulfilled(this.value);
        resolve(result);
      } catch (err) {
        reject(err);
      }
    }
    if (this.state === "rejected") {
      try {
        const result = onRejected(this.reason);
        resolve(result);
      } catch (err) {
        reject(err);
      }
    }
    if (this.state === "pending") {
      this.onFulfilledCallbacks.push(() => {
        try {
          const result = onFulfilled(this.value);
          resolve(result);
        } catch (err) {
          reject(err);
        }
      });
      this.onRejectedCallbacks.push(() => {
        try {
          const result = onRejected(this.reason);
          resolve(result);
        } catch (err) {
          reject(err);
        }
      });
    }
  });
}

这样就支持链式调用:

new MyPromise((resolve) => resolve(1))
  .then(res => res + 1)
  .then(res => console.log(res)); // 输出 2

五、Promise 实际应用案例

1. 并发请求优化

async function getData() {
  const [user, posts] = await Promise.all([
    fetch("/user").then(r => r.json()),
    fetch("/posts").then(r => r.json())
  ]);
  console.log(user, posts);
}

2. 网络请求失败重试

function fetchWithRetry(url, times = 3) {
  return new Promise((resolve, reject) => {
    function attempt(n) {
      fetch(url).then(resolve).catch(err => {
        if (n === 0) reject(err);
        else attempt(n - 1);
      });
    }
    attempt(times);
  });
}

六、Promise 与 async/await 的关系

  • async/await 是基于 Promise 的语法糖。
  • await 后面必须是 Promise 或值。
  • try/catch 捕获异常,更直观。
async function loadData() {
  try {
    const data = await fetch("/api").then(r => r.json());
    console.log(data);
  } catch (err) {
    console.error(err);
  }
}

七、总结

  • Promise 解决了异步回调地狱问题。
  • 核心机制是 状态不可逆 + 微任务调度
  • 掌握手写 Promise,有助于深入理解其原理。
  • 在现代开发中,Promise 与 async/await 是前端必备技能。

八、参考资料