一文搞懂Promise原理和实现

96 阅读5分钟

什么是Promise

Promise 是 JavaScript 中用于处理异步操作的对象,它代表一个尚未完成但预计将来会完成的操作。

Promise的运行过程

new MyPromise((resolve,reject) => {
    setTimeout(resolve("test"),1000)
}).then(res){
    console.log("then回调: "+res)
}

首先需要知道JS的事件机制

宏任务:script,setTimeout,setInterval

微任务:Promise,then

优先级:主线程>微任务>宏任务

也就是说在当前代码中,时延函数中代码最晚运行,此时先执行then,将then中的函数存入一个微任务队列中,等实验代码中的resolve执行完毕后,才运行then中的函数

  1. 同步代码执行阶段

    • 创建 Promise 实例,立即执行 executor 函数
    • 遇到 setTimeout,将其回调函数放入宏任务队列
    • 执行 .then() 方法,将成功回调函数存入微任务队列
  2. 事件循环处理阶段

    • 主线程执行完毕
    • 检查微任务队列,发现 Promise 的 then 回调在等待
    • 但此时 Promise 仍处于 pending 状态,回调暂不执行
  3. 异步操作完成阶段

    • 1秒后,setTimeout 回调进入宏任务队列
    • 执行 resolve("操作成功"),Promise 状态变为 fulfilled
    • 触发 then 回调执行,输出:"then回调: 操作成功"

简易Promise实现

首先创建一个Promise类的大体架构,在上文大概已经知道,Promise中存在构造函数,resolve,reject,then这几个内部函数,以及状态值,value值和任务队列

class MyPromise {
    #state = 'pending'
    #value = null
    //任务队列
    #onFulfilledCallBacks = [];
    #onRejectedCallBacks = [];

    constructor(excutor) {
        try {
            excutor()
        } catch(err) {
            console.log(err)
        }
    }
    
    #resolve(res) {
        
    }
    #reject(err) {
        
    }
    #then(onSuccess,onFail) {
    
    }
}

实现构造函数

constructor(excutor) {
    try {
        excutor(this.#resolve.bind(this),this.#reject.bind(this))
    }catch(err) {
        this.#reject(err)
    }
}

excutor(this.#resolve.bind(this),this.#reject.bind(this)),将resolvereject正确绑定到执行上下文中。如果不绑定,调用resolve时,resolve中的this会指向undefined

前后两个this的问题

两个this都指向new MyPromise这一实例对象,由于this.#resolve是引用了这个函数,并不是调用,并不能将resolve正确绑定到实例对象上,所以需要bind绑定this

实现resolve

将 Promise 对象的状态从 PENDING 变为 FULFILLED,并执行成功后的注册任务

#resolve(res) {
    if(this.#state === 'pending') {
        this.#state = 'fufilled'
        this.#value = res
        
        this.#onFulfilledCallBacks.forEach((callback) => callback(res))
    }
}

这里的callback是then中的回调函数

实现reject

将 Promise 对象的状态从 PENDING 变为 REJECTED,并执行失败后的注册任务

#reject(err) {
    if(this.#state === 'pending') {
        this.#state = 'rejected'
        this.#value = err
        
        this.#onRejectedCallBacks.forEach((callback) => callback(err))
    }
}

实现then

因为then可能会需要链式调用,所以它需要返回一个Promise的对象,并且then中的回调也可能是一个Promise的对象,这时就需要考虑通过递归处理内部的Promise

then(onSuccess, onFail) {
    return new MyPromise((resolve, reject) => {
      if (this.status === "pending") {
        if (typeof onSuccess === "function") {
          this.#onFulfilledCallBacks.push(() => {
            const fulfilledRet = onSuccess(this.value);
            // 判断then中的回调函数是不是一个Promise对象,如果是,那么需要递归再进行处理
            if (fulfilledRet instanceof MyPromise) {
                //递归入口
              fulfilledRet.then(resolve, reject); 
            } else {
              resolve(fulfilledRet);
            }
          });
        }

        if (typeof onFail === "function") {
          this.#onRejectedCallBacks.push(() => {
            const rejectReason = onFail(this.value);

            if (rejectReason instanceof MyPromise) {
              rejectReason.then(resolve, reject);
            } else {
              reject(rejectReason);
            }
          });
        }
      }

      if (this.status === "fulfilled") {
        const fulfilledRet = onSuccess(this.value);

        if (fulfilledRet instanceof MyPromise) {
          fulfilledRet.then(resolve, reject);
        } else {
          resolve(fulfilledRet);
        }
      }
      if (this.status === "rejected") {
        const rejectReason = onFail(this.value);

        if (rejectReason instanceof MyPromise) {
          rejectReason.then(resolve, reject);
        } else {
          reject(rejectReason);
        }
      }
    });
  }

这一段代码非常巧妙非常重要,深入理解

this.#onFulfilledCallBacks.push(() => {
   const fulfilledRet = onSuccess(this.value);
     // 判断then中的回调函数是不是一个Promise对象,如果是,那么需要递归再进行处理
   if (fulfilledRet instanceof MyPromise) {
       //递归入口
       fulfilledRet.then(resolve, reject); 
   } else {
       resolve(fulfilledRet);
   }
});

向队列中压入了一个箭头函数,作用是:

1. 延迟执行时机
// ❌ 错误方式:直接存储函数引用
this.#onFulfilledCallBacks.push(onSuccess);

// ✅ 正确方式:包装成回调函数
this.#onFulfilledCallBacks.push(() => {
  const fulfilledRet = onSuccess(this.value);
  // 处理逻辑...
});

区别:

  • 直接存储 onSuccess:执行时无法访问最新的 this.value
  • 包装回调:执行时能访问到 Promise 完成后的最新值
2. 确保正确的上下文访问
const promise = new MyPromise(resolve => {
  setTimeout(() => resolve("最终值"), 1000);
});

promise.then(value => {
  console.log(value); // 需要确保这里拿到的是 "最终值",而不是 undefined
});

执行时机对比:

// 如果直接 push(onSuccess)
// 当 resolve("最终值") 被调用时:
this.value = "最终值";
this.#onFulfilledCallBacks.forEach(callback => callback()); 
// 此时 callback 就是 onSuccess,但 this.value 已经正确设置

// 实际上直接 push(onSuccess) 也能工作,但...
3. 真正的关键:统一返回值处理逻辑

这才是包装回调的核心价值

this.#onFulfilledCallBacks.push(() => {
  const fulfilledRet = onSuccess(this.value);  // 1. 执行用户回调
  
  // 2. 统一处理返回值(这才是关键!)
  if (fulfilledRet instanceof MyPromise) {
    fulfilledRet.then(resolve, reject);  // Promise 值:状态传递
  } else {
    resolve(fulfilledRet);               // 普通值:直接 resolve
  }
});
4. 为什么不能直接存储 onSuccess

如果直接存储 onSuccess,就需要在别处处理返回值:

// ❌ 混乱的实现
this.#onFulfilledCallBacks.push(onSuccess);

// 然后在 resolve 方法中:
resolve(value) {
  this.value = value;
  this.#onFulfilledCallBacks.forEach(callback => {
    const result = callback(this.value);  // 执行回调
    // 但这里无法访问新Promise的resolve/reject!
    // 因为新Promise是在then方法中创建的
  });
}
5. 闭包捕获 resolve/reject

包装回调通过闭包捕获了新 Promise 的 resolve/reject

then(onSuccess, onFail) {
  return new MyPromise((resolve, reject) => {  // 新Promise的resolve/reject
    if (this.status === "pending") {
      this.#onFulfilledCallBacks.push(() => {
        // 这个箭头函数捕获了外部的resolve和reject!
        const fulfilledRet = onSuccess(this.value);
        
        if (fulfilledRet instanceof MyPromise) {
          fulfilledRet.then(resolve, reject);  // 使用捕获的resolve/reject
        } else {
          resolve(fulfilledRet);               // 使用捕获的resolve
        }
      });
    }
  });
}
6. 实际执行流程演示
const promise = new MyPromise(resolve => {
  setTimeout(() => resolve(10), 1000);
});

const promise2 = promise.then(value => {
  console.log("拿到值:", value); // 10
  return value * 2;
});

// 执行过程:
// 1. promise.then 被调用,创建新Promise,包装回调被push到队列
// 2. 1秒后 promise resolve(10)
// 3. 执行包装的回调:onSuccess(10) → 返回20
// 4. 执行 resolve(20) → promise2 变为 fulfilled,值为20
总结

使用回调函数包装的核心意义

  1. 统一处理:将用户回调执行和返回值处理封装在一起
  2. 闭包捕获:通过闭包捕获新 Promise 的 resolve/reject,用于处理返回值
  3. 时机控制:确保在 Promise 状态确定后才执行用户回调
  4. 值传递:正确处理普通值和 Promise 值的不同传递方式

实现catch

catch底层与then一样,直接使用then即可

 catch(onFail) {
    return this.then(null, onFail);
 }