Promise深度解析,以及简易版的手写实现

Promise 链式调用深度解析

📚 目录

  1. 核心示例
  2. 完整执行流程
  3. 关键设计点
  4. 常见错误:作用域陷阱

📌 Promise1 源码实现

完整的 Promise1 实现代码:

typescript 复制代码
class Promise1 {
    private status: string = 'pending'
    private value: any
    private reason: any
    private callbacks: any[] = []
    
    constructor(executor: (resolve: (value: any) => void, reject: (reason: any) => void) => void) {
        const resolve = (value: any) => {
            if (this.status !== 'pending') return
            this.status = 'fulfilled'
            this.value = value
            this.callbacks.forEach(cb => {
                queueMicrotask(() => {
                    cb.onFulfilled(this.value)
                })
            })
        }
        
        const reject = (reason: any) => {
            if (this.status !== 'pending') return
            this.status = 'rejected'
            this.reason = reason
            this.callbacks.forEach(cb => {
                queueMicrotask(() => {
                    cb.onRejected(this.reason)
                })
            })
        }
        
        try {
            executor(resolve, reject) // 把resolve的控制权交给用户
        } catch (error) {
            reject(error)
        }
    }
    
    then(onFulfilled: (value: any) => any, onRejected: (reason: any) => void) {
        return new Promise1((resolve: (value: any) => void, reject: (reason: any) => void): any => {
            const handleCallback = (callback: (value: any) => any, value: any) => {
                try {
                    const result = callback(value)
                    if (result instanceof Promise1) {
                        result.then(resolve, reject)
                    } else {
                        resolve(result)
                    }
                }
                catch (error) {
                    reject(error)
                }
            }
            
            if (this.status === 'pending') {
                this.callbacks.push({
                    onFulfilled: (value: any) => handleCallback(onFulfilled, value),
                    onRejected: (reason: any) => handleCallback(onRejected, reason)
                })
            }
            else if (this.status === 'fulfilled') {
                queueMicrotask(() => {
                    handleCallback(onFulfilled, this.value)
                })
            }
            else if (this.status === 'rejected') {
                queueMicrotask(() => {
                    onRejected(this.reason)
                })
            }
        })
    }
}

代码关键点注解

行号 代码 说明
2-5 private status/value/reason/callbacks Promise 的私有状态
7-16 const resolve = (value) => {...} resolve 函数(箭头函数捕获 this)
9 this.status = 'fulfilled' 修改状态为已完成
10 this.value = value 保存 resolve 的值
11-15 this.callbacks.forEach(...) 触发所有已注册的回调
12 queueMicrotask(...) 异步执行回调(微任务)
28 executor(resolve, reject) 执行用户传入的 executor
35 const handleCallback = ... 核心:处理回调、捕获 resolve/reject
38-39 if (result instanceof Promise1) Promise 扁平化:检查返回值
39 result.then(resolve, reject) 关键:把外层的 resolve/reject 传给内层
49-53 if (this.status === 'pending') pending 状态:注册回调
55-59 else if (this.status === 'fulfilled') fulfilled 状态:立即执行(异步)

核心示例

使用 Promise1 实现链式调用:

javascript 复制代码
// 创建 p1
const p1 = new Promise1((resolve) => resolve(10));

// p1.then 返回 p2
const p2 = p1.then((v1) => {
  // 返回一个 Promise (innerPromise)
  return new Promise1((resolve) => resolve(100));
});

// p2.then 返回 p3
const p3 = p2.then((v2) => {
  console.log(v2); // 输出: 100
});

核心机制 :当 .then() 回调返回一个 Promise 时,会执行:

javascript 复制代码
innerPromise.then(resolve_p2, reject_p2);
//                ^^^^^^^^^  ^^^^^^^^^
//                把外层 Promise 的 resolve/reject 作为内层 Promise 的回调

💭 个人总结(完整执行流程概述)

主要流程

首先 p1 执行 executor,即 resolve => resolve(10),然后修改 p1.status = 'fulfilled',发现此时回调还未注册,继续执行。

调用 p1.then 注册 p1 的回调并返回 p2 。当 p2 的回调注册时,由于调用的是 p1.then,所以 this 指向 p1。在 then 执行时发现 this.status(即 p1 的状态)是 fulfilled,然后立刻执行回调(异步),加入微任务队列。

继续执行注册 p2 的回调,返回 p3

微任务执行

现在主线程空闲,执行微任务(p1 的回调):

javascript 复制代码
handleCallback(onFulfilled, this.value);
// this 指向 p1,所以 this.value = 10

onFulfilled 是:

javascript 复制代码
(v1) => {
  return new Promise1((resolve) => resolve(100));
};

所以变成了:

javascript 复制代码
handleCallback((v1) => {
  return new Promise1((resolve) => resolve(100));
}, 10);

handleCallback 中执行:

javascript 复制代码
10 => {
    return new Promise1(resolve => resolve(100));
}

注意 :这里 10 不会被传递下去,返回了一个 Promise,现在统称为 innerPromise

Promise 扁平化的关键

由于 handleCallback 定义在 p2 中,所以它通过闭包捕获了 p2 的 resolvereject 的引用(现在称为 resolve_p2reject_p2)。

发现 result 是一个 innerPromise,所以执行:

javascript 复制代码
innerPromise.then(resolve_p2, reject_p2);

传入 resolve_p2reject_p2 作为 innerPromise 的 onFulfilledonRejected

innerPromise 的执行

执行 return new innerPromise(executor),这里的 executor 就是 resolve => resolve(100)

执行 executor,但这里的 resolve 和 reject 是 innerResolveinnerReject。然后执行把 100 保存到 innerPromise.value 上,修改 innerPromise.status = 'fulfilled',执行 innerCallbacks(但此时还不存在)。

接着调用 innerPromise.then(resolve_p2, reject_p2),返回一个 innerInnerPromise,然后把 resolve_p2reject_p2 通过 handleCallback 包装一次。

由于是 innerPromise.then,所以此时 this 指向 innerPromiseinnerPromise.statusfulfilled,然后执行回调:

javascript 复制代码
queueMicrotask(() => {
  handleCallback(onFulfilled, this.value);
});

神奇之处:两个 this

这里就是设计的神奇之处!

onFulfilledresolve_p2resolve_p2 是一个箭头函数,它内部的 this 指向定义时的上下文(p2)。

执行回调时:

javascript 复制代码
handleCallback(onFulfilled, this.value);
//             ^^^^^^^^^^^  ^^^^^^^^^^
//             resolve_p2   innerPromise.value

注意有两个 this

  • 参数里的 thisinnerPromise 的 this,所以 this.value = 100
  • resolve_p2 内部的 this 是 p2,所以执行 resolve_p2(100) 时会把值 100 赋给 p2.value

Promise 扁平化完成 :通过 p2 的 resolve 把看似内部的链条又重新链接回了外部!

最后阶段

现在 p2 的 resolve 执行了,p2.status = 'fulfilled',所以执行 p2 的回调:

javascript 复制代码
(v2) => {
  console.log(v2);
};

这个回调注册在 p2.callbacks 上,通过遍历数组得到 onFulfilled,然后:

javascript 复制代码
cb.onFulfilled(this.value);
// this 指向 p2(因为是 p2 的 resolve 触发的,别以为是 p3!)
// this.value = 100

输出 100,完成整个链条!

🚨 常见错误

我之前犯过的错误

如果最后一步 .then 是:

javascript 复制代码
.then(v2 => {
    console.log(v2);
    resolve(111)  // ❌ 错误!
})

这是同样的错误 :试图在外部定义中获取 resolve,但它实际上并不存在!

问题本质

v2 => { console.log(v2); resolve(111) } 是作为 then 的参数 onFulfilled 传入的,但这个 resolve

  1. 既不存在于 p2 :p2 已经执行完了,p2 的 resolve 在外部获取不到
  2. 也不存在于 p3:虽然这是 p2 的回调,但运行在 p3 的环境中
  3. 作用域是独立的:它的执行环境独立于 p2 和 p3

类比

javascript 复制代码
function fn() {
  console.log(value); // ❌ 找不到 value
}

function fn1(value) {
  fn(); // 在 fn1 中调用,但 fn 访问不到 fn1 的 value
}

正确做法:返回新的 Promise,通过参数获取 resolve:

javascript 复制代码
.then(v2 => {
    console.log(v2);
    return new Promise1(resolve => {
        resolve(111)  // ✅ 通过参数获取
    })
})


完整执行流程

阶段 1:同步执行阶段

步骤 1:创建 p1
javascript 复制代码
const p1 = new Promise1((resolve) => resolve(10));

执行过程

  1. 执行 executor:resolve => resolve(10)
  2. 调用 resolve(10)
  3. 修改 p1.status = 'fulfilled'
  4. 保存 p1.value = 10
  5. 遍历 p1.callbacks(此时为空,没有回调需要触发)

状态

  • p1.status = 'fulfilled'
  • p1.value = 10
  • p1.callbacks = []

步骤 2:调用 p1.then,创建 p2
javascript 复制代码
const p2 = p1.then((v1) => {
  return new Promise1((resolve) => resolve(100));
});

执行过程

  1. 调用 p1.then(onFulfilled)

    • this 指向 p1(因为是 p1.then
    • onFulfilled = v1 => { return new Promise1(...) }
  2. 检查 this.status(即 p1.status

    • 发现是 'fulfilled'
    • 进入分支:
    javascript 复制代码
    else if (this.status === 'fulfilled') {
        queueMicrotask(() => {
            handleCallback(onFulfilled, this.value)
        })
    }
  3. 将回调加入微任务队列

    • 微任务队列[任务A: handleCallback(onFulfilled, 10)]
  4. 返回 p2(pending 状态)

状态

  • p2.status = 'pending'
  • p2.value = undefined
  • 微任务队列:[任务A]

步骤 3:调用 p2.then,创建 p3
javascript 复制代码
const p3 = p2.then((v2) => {
  console.log(v2);
});

执行过程

  1. 调用 p2.then(onFulfilled)

    • this 指向 p2
    • onFulfilled = v2 => console.log(v2)
  2. 检查 this.status(即 p2.status

    • 发现是 'pending'
    • 进入分支:
    javascript 复制代码
    if (this.status === "pending") {
      this.callbacks.push({
        onFulfilled: (value) => handleCallback(onFulfilled, value),
        onRejected: (reason) => handleCallback(onRejected, reason),
      });
    }
  3. 注册回调到 p2.callbacks

状态

  • p3.status = 'pending'
  • p2.callbacks = [{ onFulfilled: ... }]
  • 微任务队列:[任务A]

同步阶段结束,进入微任务阶段。


阶段 2:微任务阶段 1 - 执行 p1 的回调

任务 A:handleCallback(onFulfilled, 10)
javascript 复制代码
handleCallback((v1) => {
  return new Promise1((resolve) => resolve(100));
}, 10);

执行过程

  1. 执行回调

    javascript 复制代码
    const result = onFulfilled(10);
    // = (v1 => { return new Promise1(...) })(10)
  2. 创建 innerPromise

    javascript 复制代码
    const innerPromise = new Promise1((resolve) => resolve(100));
    • 执行 executor:resolve => resolve(100)
    • 调用 resolve(100)(这是 innerPromise 的 resolve)
    • innerPromise.status = 'fulfilled'
    • innerPromise.value = 100
    • innerPromise.callbacks = [](空)
  3. 检查返回值

    javascript 复制代码
    if (result instanceof Promise1) {
      // result = innerPromise
      result.then(resolve, reject);
      //          ^^^^^^^  ^^^^^^
      //          p2 的 resolve 和 reject
    }

    关键设计点

    • handleCallback 定义在 p2 的构造器中
    • 通过闭包捕获了 resolve_p2reject_p2
    • 现在将它们作为回调传递给 innerPromise
  4. 执行 innerPromise.then(resolve_p2, reject_p2)

    创建 innerInnerPromise

    javascript 复制代码
    const innerInnerPromise = innerPromise.then(resolve_p2, reject_p2);

    内部执行

    • this 指向 innerPromise
    • this.status = 'fulfilled'
    • 进入分支:
    javascript 复制代码
    else if (this.status === 'fulfilled') {
        queueMicrotask(() => {
            handleCallback(onFulfilled, this.value)
            //             ^^^^^^^^^^^  ^^^^^^^^^^
            //             resolve_p2   innerPromise.value = 100
        })
    }
    • 将新任务加入微任务队列
    • 微任务队列[任务B: handleCallback(resolve_p2, 100)]

状态

  • innerPromise.status = 'fulfilled'
  • innerPromise.value = 100
  • 微任务队列:[任务B]

阶段 3:微任务阶段 2 - 执行 innerPromise 的回调

任务 B:handleCallback(resolve_p2, 100)
javascript 复制代码
handleCallback(resolve_p2, 100);

神奇之处 :这里有两个 this

执行过程

  1. 调用参数中的 this.value

    javascript 复制代码
    handleCallback(onFulfilled, this.value);
    //                          ^^^^^^^^^^
    //                          这里的 this = innerPromise
    //                          this.value = 100
  2. 执行 onFulfilled(即 resolve_p2)

    javascript 复制代码
    const result = onFulfilled(100);
    // = resolve_p2(100)
  3. resolve_p2 内部的 this

    javascript 复制代码
    const resolve_p2 = (value) => {
      // 这是箭头函数,this 指向定义时的上下文
      // 定义时在 p2 的构造器中
      // 所以 this = p2
    
      if (this.status !== "pending") return;
      this.status = "fulfilled"; // p2.status = 'fulfilled'
      this.value = value; // p2.value = 100
    
      // 触发 p2 的回调
      this.callbacks.forEach((cb) => {
        queueMicrotask(() => {
          cb.onFulfilled(this.value);
        });
      });
    };
  4. p2 的状态改变

    • p2.status = 'fulfilled'
    • p2.value = 100值从 innerPromise 传递到 p2!
    • 将 p2 的回调加入微任务队列
    • 微任务队列[任务C: p2 的回调]

关键理解

  • handleCallback(onFulfilled, this.value) 中:
    • 参数中的 this = innerPromise(调用环境)
    • onFulfilled 内部的 this = p2(定义环境,箭头函数)
  • Promise 扁平化 :通过 resolve_p2(100) 将内层 Promise 的值传递给外层 Promise

阶段 4:微任务阶段 3 - 执行 p2 的回调

任务 C:p2 的回调
javascript 复制代码
cb.onFulfilled(this.value);
// this = p2(因为是 p2.resolve 触发的)
// this.value = 100

执行过程

  1. 调用回调

    javascript 复制代码
    cb.onFulfilled(100);
    // = (value => handleCallback(v2 => console.log(v2), value))(100)
  2. 执行 handleCallback

    javascript 复制代码
    handleCallback((v2) => console.log(v2), 100);
  3. 执行用户回调

    javascript 复制代码
    const result = ((v2) => console.log(v2))(100);
    // 输出: 100
    // result = undefined
  4. resolve p3

    javascript 复制代码
    if (result instanceof Promise1) {
      // 否
    } else {
      resolve(result); // resolve_p3(undefined)
    }
    • p3.status = 'fulfilled'
    • p3.value = undefined

注意

  • cb.onFulfilled(this.value) 中的 this 指向 p2不是 p3
  • 因为这个回调是 p2 的 resolve 触发的
  • this 指向触发者(p2),不是回调注册者(p3)

执行完毕!链条结束。


关键设计点

1. 闭包捕获 resolve

javascript 复制代码
// p2 的构造器中
return new Promise1((resolve_p2, reject_p2) => {
  const handleCallback = (callback, value) => {
    // handleCallback 通过闭包捕获了 resolve_p2 和 reject_p2

    const result = callback(value);
    if (result instanceof Promise1) {
      result.then(resolve_p2, reject_p2);
      //          ^^^^^^^^^  ^^^^^^^^^
      //          使用闭包捕获的 resolve_p2
    }
  };
});

作用:让 innerPromise 完成时能够改变 p2 的状态。


2. Promise 扁平化机制

javascript 复制代码
innerPromise.then(resolve_p2, reject_p2);

流程

scss 复制代码
innerPromise resolve(100)
    ↓
触发回调 resolve_p2(100)
    ↓
p2.value = 100
    ↓
p2 的回调执行

效果

  • 用户写:p2.then(v2 => console.log(v2))
  • 得到:100(不是 Promise 对象)
  • 自动扁平化!

3. 两个 this 的区别

javascript 复制代码
handleCallback(onFulfilled, this.value);
//             ^^^^^^^^^^^  ^^^^^^^^^^
//             resolve_p2   innerPromise.value
//             (内部 this = p2)  (外部 this = innerPromise)

关键理解

  • 外部 this (调用环境):innerPromise.then 内部,this = innerPromise
  • 内部 this (定义环境):resolve_p2 是箭头函数,定义在 p2 构造器中,this = p2

4. 参数传递时机

javascript 复制代码
// 注册回调时(定义)
this.callbacks.push({
  onFulfilled: (value) => handleCallback(onFulfilled, value),
  //           ^^^^^
  //           参数,还没有值
});

// resolve 执行时(调用)
cb.onFulfilled(this.value);
//             ^^^^^^^^^^
//             传入参数,value 被赋值

关键

  • 参数在调用时赋值,不是定义时
  • value 来自 this.value(Promise 的值)

常见错误:作用域陷阱

错误示例

javascript 复制代码
p2.then((v2) => {
  console.log(v2);
  resolve(111); // ❌ ReferenceError: resolve is not defined
});

为什么会报错?

问题本质

v2 => { console.log(v2); resolve(111) } 是作为 then 的参数 onFulfilled 传入的,但这个 resolve

  1. 不存在于 p2

    • p2 的 resolve 在 p2 的构造器中
    • 构造器执行完毕后,resolve 就不在任何外部可访问的作用域中了
  2. 不存在于 p3

    • p3 的 resolve 在 p3 的构造器中
    • 但回调函数定义在外部,作用域链不包含 p3 的构造器
  3. 这是 p2 的回调

    • 回调注册在 p2.callbacks
    • 但运行在 p3 的环境中(p3 的 handleCallback)
  4. 它的作用域是独立的

    • 作用域链:[callback] → [外部作用域]
    • 不包含 p2 或 p3 的作用域

作用域分析

javascript 复制代码
// ===== 外部作用域 =====
const callback = (v2) => {
  // 作用域链:[callback] → [外部]
  console.log(v2);
  resolve(111); // ❌ 沿着作用域链找,找不到
};

// ===== p2 的构造器(已销毁)=====
new Promise1((resolve_p2, reject_p2) => {
  // resolve_p2 在这里
  // 但构造器执行完就销毁了
});

// ===== p3 的构造器 =====
new Promise1((resolve_p3, reject_p3) => {
  // resolve_p3 在这里
  // 但 callback 的作用域链不包含这里

  handleCallback(callback, value);
  // callback 执行时访问不到 resolve_p3
});

类比:函数作用域

这个错误和以下情况完全相同:

javascript 复制代码
function fn() {
  console.log(value); // ❌ ReferenceError: value is not defined
}

function fn1(value) {
  fn(); // 在 fn1 中调用,但 fn 访问不到 fn1 的 value
}

fn1(100);

核心原则

  • 函数的作用域链在定义时确定
  • 调用位置不影响作用域链
  • 即使在某个环境中调用,也访问不到该环境的变量

正确做法

如果想在异步操作后 resolve,需要返回新的 Promise:

javascript 复制代码
p2.then((v2) => {
  console.log(v2); // 100

  // ✅ 返回新 Promise,通过参数获取 resolve
  return new Promise1((resolve) => {
    //                ^^^^^^^ 参数!
    setTimeout(() => {
      resolve(111); // ✅ 可以使用
    }, 1000);
  });
});

总结

核心机制

  1. 状态判断:根据 Promise 的状态(pending/fulfilled/rejected)决定如何处理回调
  2. 闭包捕获:handleCallback 通过闭包捕获外层 Promise 的 resolve/reject
  3. Promise 扁平化innerPromise.then(resolve_p2, reject_p2) 实现值的传递
  4. 参数传递:回调的参数在 resolve 调用时赋值,不是定义时

关键理解

  • this 指向

    • 方法调用:p1.then()this = p1
    • 箭头函数:捕获定义时的 this
  • 作用域链

    • 函数的作用域链在定义时确定
    • 调用位置不改变作用域链
    • 外部定义的函数访问不到内部的变量
  • Promise 扁平化

    • 通过 result.then(resolve, reject) 将内层 Promise 的 resolve/reject 设置为外层的
    • 实现值的自动传递,用户无需手动处理嵌套

设计哲学

Promise 的设计精妙之处在于:

  1. 通过闭包和参数传递实现状态同步
  2. 通过 .then(resolve, reject) 实现 Promise 扁平化
  3. 通过箭头函数和作用域链保证 this 指向正确
  4. 通过微任务队列保证异步执行顺序

这些机制共同构成了 Promise 强大而优雅的链式调用能力!


参考代码

完整的 Promise1 实现:

typescript 复制代码
class Promise1 {
  private status: string = "pending";
  private value: any;
  private reason: any;
  private callbacks: any[] = [];

  constructor(
    executor: (
      resolve: (value: any) => void,
      reject: (reason: any) => void
    ) => void
  ) {
    const resolve = (value: any) => {
      if (this.status !== "pending") return;
      this.status = "fulfilled";
      this.value = value;
      this.callbacks.forEach((cb) => {
        queueMicrotask(() => {
          cb.onFulfilled(this.value);
        });
      });
    };

    const reject = (reason: any) => {
      if (this.status !== "pending") return;
      this.status = "rejected";
      this.reason = reason;
      this.callbacks.forEach((cb) => {
        queueMicrotask(() => {
          cb.onRejected(this.reason);
        });
      });
    };

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

  then(onFulfilled: (value: any) => any, onRejected: (reason: any) => void) {
    return new Promise1(
      (resolve: (value: any) => void, reject: (reason: any) => void): any => {
        const handleCallback = (callback: (value: any) => any, value: any) => {
          try {
            const result = callback(value);
            if (result instanceof Promise1) {
              result.then(resolve, reject);
            } else {
              resolve(result);
            }
          } catch (error) {
            reject(error);
          }
        };

        if (this.status === "pending") {
          this.callbacks.push({
            onFulfilled: (value: any) => handleCallback(onFulfilled, value),
            onRejected: (reason: any) => handleCallback(onRejected, reason),
          });
        } else if (this.status === "fulfilled") {
          queueMicrotask(() => {
            handleCallback(onFulfilled, this.value);
          });
        } else if (this.status === "rejected") {
          queueMicrotask(() => {
            onRejected(this.reason);
          });
        }
      }
    );
  }
}

这份文档总结了 Promise 链式调用的核心机制,包括执行流程、关键设计点和常见陷阱。希望能帮助你深入理解 Promise 的工作原理! 🎉

相关推荐
梦之云3 小时前
state 状态相关
前端
梦之云3 小时前
effect 副作用相关
前端
golang学习记3 小时前
从0死磕全栈之Next.js 生产环境优化最佳实践
前端
Mintopia4 小时前
🧠 Next.js 还是 Nuxt.js?——当 JavaScript 碰上命运的分叉路
前端·后端·全栈
5pace4 小时前
Mac Nginx安装、启动、简单命令(苍穹外卖、黑马点评前端环境搭建)
java·前端·nginx·macos·tomcat
Learn Beyond Limits4 小时前
如何在Mac进行Safari网页长截图?
前端·macos·safari·方法·操作·功能·开发者平台
阿珊和她的猫4 小时前
深入剖析 Vue Router History 路由刷新页面 404 问题:原因与解决之道
前端·javascript·vue.js
IT_陈寒5 小时前
Vue3性能提升30%的秘密:5个90%开发者不知道的组合式API优化技巧
前端·人工智能·后端
我是华为OD~HR~栗栗呀5 小时前
华为od-22届考研-C++面经
java·前端·c++·python·华为od·华为·面试