"深入理解:手把手教你实现 Promise"

332 阅读8分钟

前言

  • 本文会从零开始带你手撕一个简易的 Promise。
  • 我把 Promise 的实现拆分为如下几个步骤, 每个步骤从基本使用到代码实现, 一点一点地实现一个完整 Promise:
      1. Promise 初见雏形
      1. then 方法初见雏形
      1. 执行器函数中出现异步代码
      1. then 方法接收的回调中返回普通值
      1. then 方法接收的回调中抛出错误
      1. then 方法接收的回调中返回自己
      1. then 方法接收的回调中返回新的 promise
      1. resolve 方法中传入 promise

本文最后附上该文章实现的简易 Promise 完整代码~~~

1. Promise 初见雏形

基本使用

const { MyPromise } = require("./MyPromise");

let p1 = new MyPromise((resolve, reject) => {
  resolve("成功了");
  // reject('失败了')
  // throw new Error('出错了')
});

console.log(p1);

手写实现

const PENDING = "PENDING";
const RESOLVE = "RESOLVE";
const REJECT = "REJECT";

class MyPromise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;

    let resolve = (value) => {
      if (this.status === PENDING) {
        this.status = RESOLVE;
        this.value = value;
      }
    };

    let reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECT;
        this.reason = reason;
      }
    };

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

module.exports = {
  MyPromise,
};

2. then 方法初见雏形

基本使用

// const {
//     MyPromise
// } = require('./MyPromise')

let p1 = new Promise((resolve, reject) => {
  resolve("成功了");
  // reject('失败了')
  // throw new Error('出错了')
});

p1.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

p1.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);

上述代码中通过 p1 调用了两次 then 方法, 他们都会执行

手写实现

class MyPromise {
  // ......

+  // 原型上的方法, 供给实例调用
+  then(onFulfilled, onRejected) {
+    if (this.status === RESOLVE) {
+      onFulfilled(this.value);
+    }
+
+    if (this.status === REJECT) {
+      onRejected(this.reason);
+    }
+  }
}

3. 执行器函数中出现异步代码

基本使用

import MyPromise from "./MyPromise";

let p1 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("成功了");
    // reject('失败了')
    // throw new Error("出错了");
  }, 2000);
});

p1.then(
  (value) => {
    console.log(value);
  },
  (reason) => {
    console.log(reason);
  }
);
  • 先来看下上面的代码,在执行器函数中出现了异步代码 setTimeout(),如果使用面我们目前实现的 MyPromise,那么会发现什么都没有输出
  • 这是因为通过 p1 调用了 then 方法,此时 p1 这个 promise 实例的状态仍还没有发生改变,而是两秒后改变。即仍为 pending 状态,所以我们需要在源码实现中维护相应的回调, 等到可以调用时再取出回调然后执行。

手写实现

class MyPromise {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined

+        // 维护成功的回调
+        this.onResolvedCallbacks = []
+        // 维护失败的回调
+        this.onRejectedCallbacks = []

        let resolve = (value) => {
            if (this.status === PENDING) {
                this.status = RESOLVE
                this.value = value

+                // 等到异步代码执行完毕后,再取出相应的回调然后执行
+                this.onResolvedCallbacks.forEach(fn => fn())
            }
        }

        let reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECT
                this.reason = reason
+                // 等到异步代码执行完毕后,再取出相应的回调然后执行
+                this.onRejectedCallbacks.forEach(fn => fn())
            }
        }

        // ......
    }

    then(onFulfilled, onRejected) {
        if (this.status === RESOLVE) {
            onFulfilled(this.value)
        }

        if (this.status === REJECT) {
            onRejected(this.reason)
        }

+        // 由于用户在执行器函数中写了异步代码, 随后调用 then 方法, 此时 promise 实例(p1)的状态还是 PENDING
+        if (this.status === PENDING) {
+            // 将用户传给 then 方法的第一个参数(成功的回调)存起来
+            this.onResolvedCallbacks.push(() => {
+                onFulfilled(this.value)
+            })
+
+            // 将用户传给 then 方法的第一个参数(成功的回调)存起来
+            this.onRejectedCallbacks.push(() => {
+                onRejected(this.reason)
+            })
+        }

    }
}

4. then 方法接收的回调中返回 “普通值”

基本使用

  • 什么是 “普通值”:除了 promise 实例和抛出错误以外,其他的情况都认为是返回“普通值”
  • 如果返回的是 “普通值”,则会走到下一个 then 方法第一个参数的回调。
const { MyPromise } = require("./MyPromise");

let p1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 调用 resolve, 则参数会被传递到下一个 then 方法第一个回调的参数
    resolve("2秒后失败了");
  }, 2000);

  // resolve('成功了')
  // reject('失败了')
});

let p2 = p1.then(
  (value) => {
    console.log(value);

    // 下面返回 100,就是普通值。如果没有返回值,则是undefined,也算是普通值
    // 该 100 会传到下一个 then 方法的第一个回调的参数
    return 100;
  },
  (reason) => {
    return -100;
  }
);

p2.then(
  (value) => {
    console.log("value:", value);
  },
  (reason) => {
    console.log("reason", reason);
  }
);

手写实现

  • 在源码实现中, 我们需要完成两件事:
      1. 由于 p1.then() 的返回值 p2 仍然可以调用 then(), 因此得知 then() 需要返回一个 promise 实例
      1. 用户会给 then() 传入两个回调, 这两个回调可能会有返回值, 我们需要根据返回值的类型(即 “普通值” or 抛出错误 or 返回 promise)做出不同的处理。
class MyPromise {
    // ......

    then(onFulfilled, onRejected) {

+        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status === RESOLVE) {
+                let x = onFulfilled(this.value)
+                // 注意:这里需要再次调用 resolve, 更新此 promise2 的 value 值,以便继续传递下去。下面同理!
+                resolve(x)
            }

            if (this.status === REJECT) {
+                let x = onRejected(this.reason)
+                reject(x)
            }

            if (this.status === PENDING) {
                this.onResolvedCallbacks.push(() => {
+                    let x = onFulfilled(this.value)
+                    resolve(x)
                })

                this.onRejectedCallbacks.push(() => {
+                    let x = onRejected(this.reason)
+                    reject(x)
                })
            }
        })

+        return promise2
    }
}

5. then 方法接收的回调中抛出错误

基本使用

// let {
//     MyPromise
// } = require("./myPromise");

let p1 = new Promise((resolve, reject) => {
  resolve("成功啦");
});

let p2 = p1.then(
  (data) => {
    // 这里抛出错误, 会传递到下一个then回调中第二个参数
    throw new Error("error了");
  },
  (err) => {
    return "失败呵呵";
  }
);

p2.then(
  (data) => {
    console.log(data);
  },
  (err) => {
    console.log("出错了-------", err);
  }
);

手写实现

  • 这一步的实现我把它拆为如下四个步骤:

      1. 定义一个 resolvePromise 方法用于解析返回值
      1. resolvePromise 方法的初步实现
      1. 开启一个定时器
      1. 使用 try...catch 捕获错误
+// 2. resolvePromise 方法的初步实现:如果 x 是一个新的 promise,那么通过这个方法,判断该新 promise 是成功还是失败,成功+则调用 promise2 的 resolve, 失败则调用 promise2 的 reject
+const resolvePromise = (promise2, x, resolve, reject) => {
+  console.log(promise2);
+  console.log(x);
+  console.log(resolve);
+  console.log(reject);
+};

class MyPromise {
  // ......

  then(onFulfilled, onRejected) {
    let promise2 = new MyPromise((resolve, reject) => {
      // ......

      if (this.status === RESOLVED) {
+        // 3. 开启一个定时器:由于下面要将 promise2 传给 resolvePromise(),而 promise2 的创建需要该执行器函数的代码全部执行才能获取到,为了保证能获取到 promise2,需要开启一个定时器
+        setTimeout(() => {
+          // 4. 使用 try...catch 捕获错误:由于上边的第一个try...catch无法捕获到错误,所以需要在这里捕获下
+          try {
+            let x = onFulfilled(this.value);
+
+            // 1. 定义一个 resolvePromise 方法用于解析返回值:该方法会根据用户的返回值,进行相应的状态处理
+            resolvePromise(promise2, x, resolve, reject);
+          } catch (e) {
+            reject(e);
+          }
+        }, 0);
      }

      if (this.status === REJECTED) {
+        // 3. 开启一个定时器:同理
+        setTimeout(() => {
+          // 4. 使用 try...catch 捕获错误:同理
+          try {
+            let x = onRejected(this.reason);
+
+            // 1. 定义一个 resolvePromise 方法用于解析返回值:同理!
+            resolvePromise(promise2, x, resolve, reject);
+          } catch (e) {
+            reject(e);
+          }
+        }, 0);
      }

      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
+          // 3. 开启一个定时器:同理
+          setTimeout(() => {
+            // 4. 使用 try...catch 捕获错误:同理
+            try {
+              let x = onFulfilled(this.value);
+
+              // 1. 定义一个 resolvePromise 方法用于解析返回值:同理
+              resolvePromise(promise2, x, resolve, reject);
+            } catch (e) {
+              reject(e);
+            }
+          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
+          // 3. 开启一个定时器:同理
+          setTimeout(() => {
+            // 4. 使用 try...catch 捕获错误:同理
+            try {
+              let x = onRejected(this.reason);
+
+              // 1. 定义一个 resolvePromise 方法用于解析返回值:同理
+              resolvePromise(promise2, x, resolve, reject);
+            } catch (e) {
+              reject(e);
+            }
+          }, 0);
        });
      }
    });

    return promise2;
  }
}

6. then 方法接收的回调中返回自己

基本使用

let { MyPromise } = require("./myPromise");

let p1 = new Promise((resolve, reject) => {
  resolve("成功啦");
});
let p2 = p1.then(
  (data) => {
    // then 方法接收的回调中返回自己
    return p2;
  },
  (err) => {
    return "失败呵呵";
  }
);

p2.then(
  (data) => {},
  (err) => {
    console.log(err);
  }
);

手写实现

const resolvePromise = (promise2, x, resolve, reject) => {
+  // 如果用户返回的还是自己,那么就称为循环引用,即自己等待自己
+  if (promise2 === x) {
+    return reject(
+      new TypeError("我的报错:Chaining cycle detected for promise #<Promise")
+    );
+  }
};

7. then 方法接收的回调中返回新的 promise

基本使用

let { MyPromise } = require("./myPromise");

let p1 = new Promise((resolve, reject) => {
  resolve("1---成功啦");
});

let p2 = p1.then(
  (data) => {
    let p3 = new Promise((resolve, reject) => {
      resolve("2---成功啦");
    });

    // 该回调返回新的 promise,并且这个 promise 的执行器函数中调用 resolve("2---成功啦"), 此时该 promise 状态就变为成功,相应的 value 也会继续向下传递给 then 方法
    // 在源码中会执行:p3.then((data) => {}, (err) => {})
    return p3;
  },
  (err) => {
    return "失败啦";
  }
);

p2.then(
  (data) => {
    console.log(data); // 输出:'2---成功啦'
  },
  (err) => {
    console.log(err);
  }
);

手写实现

const resolvePromise = (promise2, x, resolve, reject) => {
  if (promise2 === x) {
    return reject(
      new TypeError("我的报错: Chaining cycle detected for promise #<Promise")
    );
  }

+  // 判断 x 的实际类型, x 是 onFulfilled 或 onRejected 执行的结果
+  if ((x != null && typeof x === "object") || typeof x === "function") {
+    // 说明 x 是对象类型或者函数类型
+    try {
+      // 下面取 x.then 的时候可能会报错,所以这里需要外层需要使用 try...catch 捕获
+      let then = x.then;
+      if (typeof then === "function") {
+        // 有 then 方法,那么就认为 x 是一个 promise
+        // 注意:不要写成 x.then(), 必须写成 then.call(x), 因为需要修改 then() 中 this 指向 promise 实例
+        then.call(
+          x,
+          (y) => {
+            // 注意这里:有点不太好理解,因为外边已经调用了 resolve,那么 promise 状态就变为成功,我们内部帮用户调用then 方法,帮助用户继续把 value 继续传递下去
+            // console.log(y) // 此处的 y 就是 '2---成功啦'
+            // 这里的y就是外边新promise调用resolve传入的 '佩奇'
+            resolve(y);
+          },
+          (err) => {
+            reject(err);
+          }
+        );
+      } else {
+        // 不是promsie,那么就默认当作是普通值
+        resolve(x);
+      }
+    } catch (e) {
+      reject(e);
+    }
+  }
};

8. resolve 方法中传入 promise

基本使用

let { MyPromise } = require("./myPromise");

let p1 = new Promise((resolve, reject) => {
  resolve("成功啦");
});

let p2 = p1.then(
  (data) => {
    return new Promise((resolve, reject) => {
      // 下面的 resolve 方法中传入了一个 promise 实例
      resolve(
        new Promise((resolve, reject) => {
          resolve(2000);
        })
      );
    });
  },
  (err) => {
    return "失败啦";
  }
);

p2.then(
  (data) => {
    console.log("data:", data); // 输出: data: 2000
  },
  (err) => {
    console.log("err:", err);
  }
);

手写实现

  • 这一步的实现我把它拆为如下几个步骤:
      1. 定义变量
      1. 使用递归
      1. 递归出口
      1. 最后补充
const resolvePromise = (promise2, x, resolve, reject) => {
  // ...

+  // 1. 定义变量:用来标记是否调用过
+  let called;

  if ((typeof x === "object" && x != null) || typeof x === "function") {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
+            // 3. 递归出口:为了防止调用了resolve后再调reject 或 调用了reject再调用resolve,事实上,只能调用其中一个
+            if (called) return;
+            called = true;

+            // 2. y 的值是一个 promise,使用递归:直到 resolve() 中传入的值为普通值
+            resolvePromise(promise2, y, resolve, reject);
          },
          (err) => {
+            // 3. 递归出口:同理
+            if (called) return;
+            called = true;

            reject(err);
          }
        );
      } else {
        resolve(x);
      }
    } catch (err) {
+      // 3. 递归出口:同理
+      if (called) return;
+      called = true;

      reject(err);
    }
+  } else {
+    // 4. 最后补充:这里应该补充上去,是 return 的是普通值的时候,直接调用 promise2 的 resolve,
+    resolve(x);
+  }
};

简易的 Promise 完整代码

const PENDING = "PENDING";
const RESOLVE = "RESOLVE";
const REJECT = "REJECT";

const resolvePromise = (promise2, x, resolve, reject) => {
  if (promise2 === x) {
    return reject(
      new TypeError("我的报错:Chaining cycle detected for promise #<Promise")
    );
  }

  let called;

  if ((x != null && typeof x === "object") || typeof x === "function") {
    try {
      let then = x.then;
      if (typeof then === "function") {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(promise2, y, resolve, reject);
          },
          (err) => {
            if (called) return;
            called = true;

            reject(y);
          }
        );
      } else {
        resolve(x);
      }
    } catch (err) {
      if (called) return;
      called = true;

      reject(err);
    }
  } else {
    resolve(x);
  }
};

class MyPromise {
  constructor(executor) {
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;

    this.onResolvedCallbacks = [];
    this.onRejectedCallbacks = [];

    let resolve = (value) => {
      if (this.status === PENDING) {
        this.status = RESOLVE;
        this.value = value;
        this.onResolvedCallbacks.forEach((fn) => fn());
      }
    };

    let reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECT;
        this.reason = reason;
        this.onRejectedCallbacks.forEach((fn) => fn());
      }
    };

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

  then(onFulfilled, onRejected) {
    let promise2 = new MyPromise((resolve, reject) => {
      if (this.status === RESOLVE) {
        setTimeout(() => {
          try {
            let x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.status === REJECT) {
        setTimeout(() => {
          try {
            let x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      if (this.status === PENDING) {
        this.onResolvedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onFulfilled(this.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });

        this.onRejectedCallbacks.push(() => {
          setTimeout(() => {
            try {
              let x = onRejected(this.reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }, 0);
        });
      }
    });

    return promise2;
  }
}

module.exports = {
  MyPromise,
};