手搓Promise:从原理到方法,彻底搞懂异步编程核心

0 阅读7分钟

Promise作为JavaScript异步编程的核心解决方案,彻底解决了回调地狱的问题。但很多开发者只停留在“会用”层面,对其底层原理、核心方法实现一知半解。本文将从Promise的核心原理出发,手把手实现一个完整的Promise类,并深入剖析anyfinallyallSettledresolve等核心方法的实现逻辑。

一、Promise核心原理剖析

1. Promise的核心特性

  • 三种状态:pending(等待)、fulfilled(成功)、rejected(失败),状态一旦变更不可逆;

  • 异步回调:成功/失败的回调函数会被存入对应队列,状态变更时批量执行;

  • then方法:1. 默认返回一个 promise 对象,它的状态跟随 then 前面的那个 promise 的状态一起变更2. then的回调中返回了一个promise 对象,那么then 中的promise的状态就会跟随自身回调返回的那个promise 对象的状态变更

2. 手写Promise基础骨架

先实现Promise的核心构造函数和then方法,这是所有扩展方法的基础:

class MyPromise {
  constructor(executor) {
    this.state = 'pending'
    this.onFulfilledCallbacks = [] // 存储成功回调函数的队列
    this.value = undefined    // 存储成功状态下的返回值
    this.onRejectedCallbacks = [] // 存储失败回调函数的队列
    this.reason = undefined    // 存储失败状态下的错误的值

    // 定义resolve函数:将Promise状态转为成功(fulfilled)
    const resolve = (value) => {
      if (this.state === 'pending') { // 只有状态为pending时才能修改,保证状态不可逆
        this.state = 'fulfilled'
        this.value = value
        // 批量执行所有存储的成功回调,传入成功值
        this.onFulfilledCallbacks.forEach((callback) => callback(value))
      }
    }
    // 定义reject函数:将Promise状态转为失败(rejected)
    const reject = (reason) => {
      if (this.state === 'pending') { // 只有状态为pending时才能修改,保证状态不可逆
        this.state = 'rejected'
        // 保存失败的错误的值
        this.reason = reason
        // 批量执行所有存储的失败回调,传入错误原因
        this.onRejectedCallbacks.forEach(callback => callback(reason))
      }
    }

    // 立即执行执行器函数,并传入resolve和reject方法
    executor(resolve, reject)
  }

  // then方法:Promise的核心方法,用于注册成功/失败回调,支持链式调用
  then(onFulfilled, onRejected) { // 会将 cb 存入onFulfilledCallbacks/onRejectedCallbacks
    // 兼容非函数参数:若onFulfilled不是函数,赋值为空函数,避免报错
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : () => { }
    // 兼容非函数参数:若onRejected不是函数,赋值为空函数,避免报错
    onRejected = typeof onRejected === 'function' ? onRejected : () => { }

    // then方法返回新的Promise,实现链式调用(核心:新Promise的状态由回调执行结果决定)
    const newPromise = new MyPromise((resolve, reject) => {
      // 情况1:当前Promise状态已为成功(fulfilled),直接执行成功回调
      if (this.state === 'fulfilled') {  // resolve 已经先行了(状态提前变为成功,例如写了同步代码)

        setTimeout(() => {        // 用setTimeout模拟微任务(原生Promise的then回调属于微任务)
          const result = onFulfilled(this.value)           // 执行成功回调,获取回调返回结果
          if (result instanceof MyPromise) {
            // 递归调用then,将新Promise的resolve/reject传入,实现状态透传
            result.then((res) => resolve(res), (err) => reject(err))
          } else {
            // 若返回值不是Promise,直接将新Promise转为成功态,传入返回值
            resolve(result)
          }
        })
      }

      // 情况2:当前Promise状态已为失败(rejected),直接执行失败回调
      if (this.state === 'rejected') {  // reject 已经先行了(状态提前变为失败)
        setTimeout(() => {        // 用setTimeout模拟微任务
          // 执行失败回调,获取回调返回结果
          const result = onRejected(this.reason) 
          // 若回调返回值是Promise实例,新Promise的状态跟随该实例
          if (result instanceof MyPromise) {
            result.then((res) => resolve(res), (err) => reject(err))
          } else {
            // 若返回值不是Promise,直接将新Promise转为成功态(即使原状态是失败,回调执行无异常则新状态为成功)
            resolve(result)
          }
        })
      }

      // 情况3:当前Promise状态为等待(pending),将回调存入对应队列,等待状态变更后执行
      if (this.state === 'pending') {
        // 存入成功回调,状态变为fulfilled时执行
        this.onFulfilledCallbacks.push((value) => {
          setTimeout(() => {
            // 执行成功回调,获取返回结果
            const result = onFulfilled(value)
            // 处理回调返回值,决定新Promise的状态
            if (result instanceof MyPromise) {
              result.then((res) => resolve(res), (err) => reject(err))
            } else {
              resolve(result)
            }
          })
        })
        // 存入失败回调,状态变为rejected时执行
        this.onRejectedCallbacks.push((reason) => {
          setTimeout(() => {
            // 执行失败回调,获取返回结果
            const result = onRejected(this.reason)
            // 处理回调返回值,决定新Promise的状态
            if (result instanceof MyPromise) {
              result.then((res) => resolve(res), (err) => reject(err))
            } else {
              resolve(result)
            }
          })
        })
      }

    })

    // 返回新Promise,实现链式调用
    return newPromise
  }


  catch(onRejected) {
    return this.then(undefined, onRejected)
  }
}

二、Promise核心静态方法实现

1. Promise.resolve:快速创建成功Promise

原理:静态方法,接收一个值,返回状态为fulfilled的Promise实例。

应用场景:请求拦截/响应拦截中快速包裹成功数据,简化异步处理。

static resolve(val) {
  return new MyPromise((resolve) => {
    resolve(val)
  })
}

2. Promise.any:“只要一个成功就赢”

原理:遍历Promise数组,只要有一个成功就resolve;全部失败则reject(包含所有失败原因)。

特点:优先返回最快成功的Promise,适合“多源请求取最快成功结果”场景。

static any(promises) {
  return new MyPromise((resolve, reject) => {
    const errors = [] // 存储所有失败原因
    let count = 0     // 失败计数器

    // 空数组特殊处理
    if (promises.length === 0) {
      reject(new AggregateError([], 'All promises were rejected'))
      return
    }

    promises.forEach((promise, index) => {
      // 兼容非Promise值(视为成功)
      MyPromise.resolve(promise).then(
        (res) => {
          resolve(res) // 只要一个成功,立即resolve
        },
        (err) => {
          errors[index] = err
          count++
          // 全部失败时reject
          if (count === promises.length) {
            reject(new AggregateError(errors, 'All promises were rejected'))
          }
        }
      )
    })
  })
}

3. Promise.allSettled:“等待所有结果,无论成败”

原理:等待所有Promise状态变更(成功/失败),返回一个永远成功的Promise,结果数组包含每个Promise的状态和值/原因。

应用场景:批量请求(如表单提交+数据上报),需要知道所有请求的最终状态,不因为单个失败中断。

static allSettled(promises) {
  const promiseArray = Array.from(promises) // 转数组,兼容类数组
  const result = []
  let count = 0

  return new MyPromise((resolve) => {
    if (promiseArray.length === 0) {
      resolve(result)
      return
    }

    promiseArray.forEach((promise, index) => {
      MyPromise.resolve(promise).then(
        (res) => {
          result[index] = { status: 'fulfilled', value: res }
        },
        (err) => {
          result[index] = { status: 'rejected', reason: err }
        }
      ).finally(() => {
        count++
        // 所有Promise处理完成后resolve
        if (count === promiseArray.length) {
          resolve(result)
        }
      })
    })
  })
}

4. Promise.finally:“无论成败都执行”

原理:实例方法,接收回调函数,无论原Promise状态如何,回调都会执行;返回新Promise,状态跟随原Promise(回调报错则变为失败)。

应用场景:异步请求后关闭loading、清理定时器等收尾操作。

finally(callback) {
  return this.then(
    (value) => {
      // 执行回调,兼容返回Promise的情况
      return MyPromise.resolve(callback()).then(() => value)
    },
    (reason) => {
      return MyPromise.resolve(callback()).then(() => { throw reason })
    }
  )
}

5. 扩展:Promise.race和Promise.all

Promise.race:“速度竞赛,第一个变更状态的赢”

static race(promises) {
  return new MyPromise((resolve, reject) => {
    promises.forEach((promise) => {
      MyPromise.resolve(promise).then(resolve, reject)
    })
  })
}

Promise.all:“全部成功才赢,一个失败就输”

static all(promises) {
  return new MyPromise((resolve, reject) => {
    const result = []
    let count = 0

    if (promises.length === 0) {
      resolve(result)
      return
    }

    promises.forEach((promise, index) => {
      MyPromise.resolve(promise).then(
        (res) => {
          result[index] = res
          count++
          if (count === promises.length) {
            resolve(result)
          }
        },
        (err) => {
          reject(err) // 一个失败立即reject
        }
      )
    })
  })
}

三、核心知识点总结

  1. 状态不可逆:Promise一旦从pending变为fulfilled/rejected,状态永久固定;

  2. then的链式调用then返回新Promise,状态跟随原Promise或回调返回的Promise;

  3. 微任务模拟:原生Promise的then回调属于微任务,本文用setTimeout模拟(宏任务),核心逻辑一致;

  4. 方法选型技巧

    • 取最快成功结果 → Promise.any

    • 取最快变更结果(无论成败)→ Promise.race

    • 全部成功才继续 → Promise.all

    • 需知道所有结果(无论成败)→ Promise.allSettled

    • 收尾操作 → finally

    • 快速创建成功Promise → Promise.resolve

四、测试验证

用以下代码验证手写Promise的核心功能:

// 测试Promise.any
const foo = () => new MyPromise((resolve, reject) => setTimeout(() => reject('foo失败'), 1000))
const bar = () => new MyPromise((resolve, reject) => setTimeout(() => reject('bar失败'), 500))
const baz = () => new MyPromise((resolve) => setTimeout(() => resolve('baz成功'), 1500))

MyPromise.any([foo(), bar(), baz()]).then(
  (res) => console.log('any成功:', res), // 输出:baz成功
  (err) => console.log('any失败:', err)
)

// 测试allSettled
MyPromise.allSettled([foo(), bar(), baz()]).then((res) => {
  console.log('allSettled结果:', res)
  // 输出:[{status:'rejected',reason:'foo失败'}, {status:'rejected',reason:'bar失败'}, {status:'fulfilled',value:'baz成功'}]
})

// 测试finally
baz().finally(() => console.log('finally执行')).then(res => console.log(res))
// 输出:finally执行 → baz成功

五、面试高频考点

  1. Promise的状态变更机制:为什么状态不可逆?(构造函数中只有pending状态下才会修改状态);

  2. then的链式调用原理then返回新Promise,回调返回Promise时会“透传”状态;

  3. all/race/any/allSettled的区别:重点区分“失败处理逻辑”和“返回结果格式”;

  4. finally的特性:回调无参数、返回Promise的状态规则、执行时机。

总结

Promise的核心是“状态管理+异步回调队列”,所有扩展方法都是基于这个核心的封装。通过手写Promise,不仅能深刻理解异步编程的本质,还能在面试中从容应对Promise相关的深度问题。掌握这些原理后,再看async/await会更加轻松,真正做到“知其然,更知其所以然”。