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 的工作原理! 🎉

相关推荐
To_OC7 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic8 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy8 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志8 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble9 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯10 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜11 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司11 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g11 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森11 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http