码字,杂谈

真丶深入理解JavaScript异步编程(最终章):手撸 Promise

写在前面

已经写了3篇前置内容了,主要是理解JS中的异步编程,异步的实现、以及异步的原理。今天内容较长,从最简单、最基本的内容入手,一点一点手撸一个简易的 Promise,巩固之前理解的异步原理,这才是我的目标。

手写 Promise

了解 Promise,从手动重写一个简易版的开始。

最简易的 Promise

最基本的 Promise 的样子是这样的:

new Promise((resolve, reject) => {});

那么照猫画虎写一个:

class MyPromise {
  constructor(executor) {
    this.status = "pending";
    this.value = null;

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

  resolve(val) {
    if (this.status === "pending") {
      this.status = "fulfilled";
      this.value = val;
    }
  }

  reject(err) {
    if (this.status === "pending") {
      this.status = "rejected";
      this.value = err;
    }
  }
}

这样就得到了一个最基本的样子,来试一下:

let p1 = new MyPromise((resolve, reject) => {}); // 此时为 pending 状态

let p2 = new MyPromise((resolve, reject) => {
  resolve();
}); // 此时为 fulfilled 状态

let p3 = new MyPromise((resolve, reject) => {
  reject();
}); // 此时为 rejected 状态

好像没什么毛病了。接下来实现 then 的链式操作。

then 的实现

前文已经提到过,它应该也是一个方法,所以我们继续在 MyPromise 类中添加一个 then 方法:

// 继续添加代码,已有代码不再重复
class MyPromise {
  constructor(executor) {
    // 添加两个回调接收,用于 then 的异步回调
    this.cbFulfilled = null;
    this.cbRejected = null;
  }

  then(resolve, reject) {
    // 先判断两个参数是否为函数,如果不是或者没有,给一个默认值
    if (typeof resolve !== "function") {
      resolve = () => {};
    }

    if (typeof reject !== "function") {
      reject = () => {};
    }

    // 初始状态,异步情况下会是这个状态
    if (this.status === "pending") {
      this.cbFulfilled = resolve;
      this.cbRjected = reject;
    }

    // 成功状态
    if (this.status === "fulfilled") {
      setTimeout(() => {
        try {
          resolve(this.value);
        } catch (error) {
          reject(error);
        }
      });
    }

    // 失败
    if (this.status === "rejected") {
      setTimeout(() => {
        try {
          reject(this.value);
        } catch (error) {
          reject(error);
        }
      });
    }
  }

  // 修改之前的代码
  resolve(val) {
    if (this.status === "pending") {
      this.status = "fulfilled";
      this.value = val;

      // 添加回调
      setTimeout(() => {
        this.cbFulfilled && this.cbFulfilled("timeout " + this.value);
      });
    }
  }

  reject(err) {
    if (this.status === "pending") {
      this.status = "rejected";
      this.value = err;

      // 添加回调
      setTimeout(() => {
        this.cbRejected && this.cbRejected("timeout " + this.value);
      });
    }
  }
}

这里我们通过使用 setTimeout 来对执行顺序加以控制,使回调成为一个异步调用。测试一下:

let p1 = new MyPromise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    resolve("jeremyjone");
    console.log(4);
  });
  console.log(2);
}).then(val => console.log(val));

console.log(3);

它的打印顺序:

1
2
3
4
jeremyjone

现在看上去已经和原生的效果差不多了。下一步我们让它成为链式的。

then 的链式实现

要实现链式操作,首先要明确:

  • 它本身返回一个 Promise
  • 它接收的状态并不会影响它新返回 Promise 的状态

既然是要一个 Promise,那么我们首先将 then 里面的方法包装在一个 Promise 中。然后稍微修改一下逻辑就可以实现链式操作了。

class MyPromise {
  // ... 其他代码省略

  then(resolve, reject) {
    // 先判断两个参数是否为函数,如果不是或者没有,给一个默认值
    if (typeof resolve !== "function") {
      resolve = () => {};
    }

    if (typeof reject !== "function") {
      reject = () => {};
    }

    // 将改变值的内容包装在一个新的 Promise 中
    return new MyPromise((newResolve, newReject) => {
      if (this.status === "pending") {
        this.cbFulfilled = val => {
          try {
            // 将当前 then 中的返回值,赋值给下一次的 then,并使其改变状态
            let res = resolve(val);
            newResolve(res);
          } catch (error) {
            // 当前 then 的异常,交给下一个 then 去解决,直接调用 reject 回调函数即可
            newReject(error);
          }
        };
        this.cbRejected = val => {
          try {
            let res = reject(val);
            // 当前接收的状态,并不影响下一次 then 的状态,所以当前的拒绝状态也返回成功状态给下一次,只有当前的异常才会修改为拒绝
            newResolve(res);
          } catch (error) {
            newReject(error);
          }
        };
      }

      if (this.status === "fulfilled") {
        setTimeout(() => {
          try {
            let res = resolve(this.value);
            newResolve(res);
          } catch (error) {
            newReject(error);
          }
        });
      }

      if (this.status === "rejected") {
        setTimeout(() => {
          try {
            let res = reject(this.value);
            newResolve(res);
          } catch (error) {
            newReject(error);
          }
        });
      }
    });
  }
}

上面代码替换完之后,现在 then 每次返回的将是一个 Promise,它已经可以链式操作了。我在代码中的对应行也添加了一些注释,以方便理解。

  • 1、最重要的是要返回我们的 MyPromise 对象,而且是一个新的对象。
  • 2、因为与在外部创建 MyPromise 对象不一样,它具有默认的修改状态的动作,所以我们需要对内部代码稍加修改:
      1. 接收当前回调函数的返回值,它由外部定义 then 方法时返回,如果外部的 then 方法内部并没有 return 语句,那么则为 undefined
      1. 当前接收到的状态,并不会影响下一次的状态。所以,只要没有出现异常,统一调用 resolve

在外面,我们可以通过链式操作测试一下:

new MyPromise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    // resolve("jeremyjone");
    reject("failed");
    console.log(4);
  }, 1000);
  console.log(2);
})
  .then(
    val => {
      setTimeout(() => {
        console.log(val);
      }, 1000);
      return "p1";
    },
    err => {
      console.log("err1 ", err);
      return "p1 err";
    }
  )
  .then(
    val => console.log("2 " + val),
    err => console.log("2 err ", err)
  );

console.log(3);

执行结果:

1
2
3
4
err1  timeout failed
2 timeout p1 err

可以看到,已经可以执行,而且第二个 then 中使用成功的回调方法接收了前一个 then 里面拒绝方法中的返回值。

then 的状态穿透

上面的代码中,不能实现状态穿透,也就是当前 then 不处理结果时,需要向下继续穿透,类似:

new MyPromise((resolve, reject) => {})
  .then() // 此处属于状态穿透
  .then(
    val => console.log(),
    err => console.log(err)
  );

为了解决这个问题,我们继续稍加修改上面的 then 方法。

class MyPromise {
  // ... 其他代码省略

  then(resolve, reject) {
    // 如果是成功状态,直接返回值,没有值就是空着即可
    if (typeof resolve !== "function") {
      resolve = () => this.value;
    }

    // 拒绝的状态需要处理。因为我们之前的逻辑是当前 then 的状态不会影响下一次的。现在需要除了穿透的状态,所以要先判断一下是否为函数属性
    // 添加一个判断是否为函数的属性
    const isRejectFunc = typeof reject === "function";
    if (typeof reject !== "function") {
      reject = () => this.value;
    }

    // 将改变值的内容包装在一个新的 Promise 中
    return new MyPromise((newResolve, newReject) => {
      if (this.status === "pending") {
        this.cbFulfilled = val => {
          try {
            let res = resolve(val);
            newResolve(res);
          } catch (error) {
            newReject(error);
          }
        };
        this.cbRejected = val => {
          try {
            // 两个拒绝的地方需要修改,如果是一个函数,走之前的方式。如果不是函数,则使用默认的拒绝方法将值继续向下传递
            if (isRejectFunc) {
              let res = reject(val);
              newResolve(res);
            } else {
              newReject(val || this.value);
            }
          } catch (error) {
            newReject(error);
          }
        };
      }

      if (this.status === "fulfilled") {
        setTimeout(() => {
          try {
            let res = resolve(this.value);
            newResolve(res);
          } catch (error) {
            newReject(error);
          }
        });
      }

      if (this.status === "rejected") {
        setTimeout(() => {
          try {
            // 第二个拒绝的地方
            if (isRejectFunc) {
              let res = reject(this.value);
              newResolve(res);
            } else {
              newReject(this.value);
            }
          } catch (error) {
            newReject(error);
          }
        });
      }
    });
  }
}

现在就可以实现状态的穿透了。

then 处理返回 Promise 对象

解决了上面的穿透问题,现在处理返回 Promise 对象的问题。

这个问题其实很好解决,只需要在 then 方法内部判断一下类型,然后如果是 Promise 对象等待其值,不是直接返回结果即可。

直接看修改的代码:

// ... 其他内容不写了

if (this.status === "fulfilled") {
  setTimeout(() => {
    try {
      let res = resolve(this.value);
      // newResolve(res);

      // 刚才我们直接调用回调函数,现在判断一下再调用
      if (res instanceof MyPromise) {
        res.then(newResolve, newReject);
      } else {
        newResolve(res);
      }
    } catch (error) {
      newReject(error);
    }
  });
}

其内部有 4 处相同内容,全部修改即可。现在返回的是一个 MyPromise 也不会有什么问题。

new MyPromise((resolve, reject) => {
  resolve("jeremyjone");
})
  .then(
    val => {
      console.log(val);
      return new MyPromise((resolve, reject) => {
        resolve("22222");
      });
    },
    err => console.log(err)
  )
  .then(
    val => console.log("2then ok " + val),
    err => console.log("2then err " + err)
  );

console.log("11111");

执行结果:

11111
jeremyjone
2then ok timeout 22222

一些静态方法

其实核心内容已经差不多了,现在写一些静态方法,让我们自定义的 MyPromise 看上去更像 Promise。

注意静态方法不要忘记 static

MyPromise.resolve

前面已经讲过,它返回一个成功状态,所以很简单,只需要新建一个 Promise 并返回成功即可。

class MyPromise {
  static resolve(value) {
    return new MyPromise((resolve, reject) => {
      if (value instanceof MyPromise) {
        // 如果传入的参数本身是一个 Promise,则按照其本身返回的状态返回。
        value.then(resolve, reject);
      } else {
        resolve(value);
      }
    });
  }
}

MyPromise.reject

与上面的同理,只需要修改为拒绝状态即可。

class MyPromise {
  static reject(value) {
    return new MyPromise((resolve, reject) => {
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
      } else {
        reject(value);
      }
    });
  }
}

MyPromise.all

它稍微有一些复杂,但是并不会比核心的 then 还困难。只需要一个循环,将所有值放在一个数组中,同时遇到错误直接抛出。最后,将结果返回即可。

class MyPromise {
  static all(promises) {
    const res = [];
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise instanceof MyPromise) {
          promise.then(
            val => {
              // 使用 push 会有位置影响
              res[i] = val;

              // 因为使用赋值,所以可能存在空,需要判空
              res.filter(x => !!x).length === promises.length && resolve(res);
            },
            err => reject(err)
          );
        } else {
          res[i] = promise;
        }
      }
    });
  }
}

Mypromise.race

这个更简单。它返回最快返回的值。一次循环,只要拿到了值就返回即可。

它利用了 Promise 只修改一次状态的特性,这是我们前面写过的。

class MyPromise {
  static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.map(promise => {
        if (promise instanceof MyPromise) {
          promise.then(
            val => resolve(val),
            err => reject(err)
          );
        } else {
          resolve(promise);
        }
      });
    });
  }
}

MyPromise 完整版

前面已经把大部分代码都按部分总结了,最后整理一下。

class MyPromise {
  constructor(executor) {
    this.status = "pending";
    this.value = null;
    this.cbFulfilled = null;
    this.cbRejected = null;

    try {
      executor(this._resolve.bind(this), this._reject.bind(this));
    } catch (error) {
      this._reject(error);
    }
  }

  _exec(promise, isRejectFunc, res, resolve, reject) {
    // 在 promise 中返回自身,抛出错误
    if (promise === res) throw Error("Chaining cycle detected for promise");

    try {
      if (res instanceof MyPromise) {
        res.then(resolve, reject);
      } else {
        isRejectFunc ? resolve(res) : reject(res);
      }
    } catch (error) {
      reject(error);
    }
  }

  _resolve(val) {
    if (this.status === "pending") {
      this.status = "fulfilled";
      this.value = val;

      // 添加回调
      setTimeout(() => {
        this.cbFulfilled && this.cbFulfilled(this.value);
      });
    }
  }

  _reject(err) {
    if (this.status === "pending") {
      this.status = "rejected";
      this.value = err;

      // 添加回调
      setTimeout(() => {
        this.cbRejected && this.cbRejected(this.value);
      });
    }
  }

  then(resolve, reject) {
    // 先判断两个参数是否为函数,如果不是或者没有,给一个默认值
    if (typeof resolve !== "function") {
      resolve = () => this.value;
    }

    const isRejectFunc = typeof reject === "function";
    if (typeof reject !== "function") {
      reject = () => this.value;
    }

    // 将改变值的内容包装在一个新的 Promise 中
    let p = new MyPromise((newResolve, newReject) => {
      // 初始状态,异步情况下会是这个状态
      if (this.status === "pending") {
        this.cbFulfilled = val => {
          let res = resolve(val);
          this._exec(p, true, res, newResolve, newReject);
        };
        this.cbRejected = val => {
          let res = reject(val);
          this._exec(p, isRejectFunc, res, newResolve, newReject);
        };
      }

      // 成功状态
      if (this.status === "fulfilled") {
        setTimeout(() => {
          let res = resolve(this.value);
          this._exec(p, true, res, newResolve, newReject);
        });
      }

      // 失败
      if (this.status === "rejected") {
        setTimeout(() => {
          let res = reject(this.value);
          this._exec(p, isRejectFunc, res, newResolve, newReject);
        });
      }
    });

    return p;
  }

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

  static resolve(value) {
    return new MyPromise((resolve, reject) => {
      if (value instanceof MyPromise) {
        // 如果传入的参数本身是一个 Promise,则按照其本身返回的状态返回。
        value.then(resolve, reject);
      } else {
        resolve(value);
      }
    });
  }

  static reject(value) {
    return new MyPromise((resolve, reject) => {
      if (value instanceof MyPromise) {
        value.then(resolve, reject);
      } else {
        reject(value);
      }
    });
  }

  static all(promises) {
    const res = [];
    return new MyPromise((resolve, reject) => {
      for (let i = 0; i < promises.length; i++) {
        const promise = promises[i];
        if (promise instanceof MyPromise) {
          promise.then(
            val => {
              // 使用 push 会有位置影响
              res[i] = val;

              // 因为使用赋值,所以可能存在空,需要判空
              res.filter(x => !!x).length === promises.length && resolve(res);
            },
            err => reject(err)
          );
        } else {
          res[i] = promise;
        }
      }
    });
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      promises.map(promise => {
        if (promise instanceof MyPromise) {
          promise.then(
            val => resolve(val),
            err => reject(err)
          );
        } else {
          resolve(promise);
        }
      });
    });
  }
}

测试用例:

new MyPromise((resolve, reject) => {
  console.log(1);
  setTimeout(() => {
    resolve("jeremyjone");
    // reject("failed");
    console.log(4);
  }, 1000);
  console.log(2);
})
  .then()
  .catch(err => console.log("catch:", err))
  .then(
    val => {
      setTimeout(() => {
          console.log(val);
      }, 1000);
      const p2 = new MyPromise((resolve, reject) => {
        resolve("小鹰");
      });
      console.log("111", val);
      return p2;
    },
    err => {
      console.log("err1 ", err);
      return "p1 err";
    }
  )
  .then(
    val => console.log("2 " + val),
    err => console.log("2 err ", err)
  );

console.log(3);

MyPromise.resolve("resolve jeremyjone").then(val => console.log(val));
MyPromise.reject("reject jeremyjone").then(null, err => console.log(err));

MyPromise.resolve(
  new MyPromise((resolve, reject) => {
    reject("reject in resolve");
  })
).then(
  val => console.log("rr1", val),
  err => console.log("rr2", err)
);

let p1 = new MyPromise((resolve, reject) => {
  resolve("p1");
});

let p2 = new MyPromise((resolve, reject) => {
  resolve("p2");
});

MyPromise.all([p1, p2, 3, 4]).then(
  val => console.log("all ok", val),
  err => console.log("all err", err)
);

let p3 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    resolve("p3");
  }, 1000);
});

let p4 = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    reject("p4");
  }, 1000);
});

MyPromise.race([p3, p4]).then(
  val => console.log("race ok", val),
  err => console.log("race err", err)
);

总结

通过本系列文章,你应该掌握了:

  • 异步原理
  • 事件循环
  • 宏任务与微任务
  • Promise 原理
  • Promise 链式操作
  • Promise 常用方法
  • Promise 核心实现
  • async / await 实现方式

当然,这个完整版不能和原生的比较,还有很多细节没有实现。这里只是总结一下其实现的核心,了解并掌握其实现的原理,通过这个实例,掌握异步核心,了解JS异步运行机制,才是我们应该学到的。

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注