第22题 手写Promise最简20行版本,实现异步链式调用

实现代码

    function Promise(fn) {
      this.cbs = [];
    
      const resolve = (value) => {
        setTimeout(() => {
          this.data = value;
          this.cbs.forEach((cb) => cb(value));
        });
      }
    
      fn(resolve.bind(this));
    }
    
    Promise.prototype.then = function (onResolved) {
      return new Promise((resolve) => {
        this.cbs.push(() => {
          const res = onResolved(this.data);
          if (res instanceof Promise) {
            res.then(resolve);
          } else {
            resolve(res);
          }
        });
      });
    };

核心案例

    new Promise((resolve) => {
      setTimeout(() => {
        resolve(1);
      }, 500);
    })
      .then((res) => {
        console.log(res);
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve(2);
          }, 500);
        });
      })
      .then(console.log);

本文将围绕这个最核心的案例来讲,这段代码的表现如下:

  • 500ms 后输出 1
  • 500ms 后输出 2

构造函数

首先来实现 Promise 构造函数

    function Promise(fn) {
      // Promise resolve时的回调函数集
      this.cbs = [];
    
      // 传递给Promise处理函数的resolve
      // 这里直接往实例上挂个data
      // 然后把onResolvedCallback数组里的函数依次执行一遍就可以
      const resolve = (value) => {
        // 注意promise的then函数需要异步执行
        setTimeout(() => {
          this.data = value;
          this.cbs.forEach((cb) => cb(value));
        });
      }
    
      // 执行用户传入的函数 
      // 并且把resolve方法交给用户执行
      fn(resolve.bind(this));
    }

好,写到这里先回过头来看案例

    const fn = (resolve) => {
      setTimeout(() => {
        resolve(1);
      }, 500);
    };
    
    new Promise(fn);

分开来看,fn 就是用户传的函数,这个函数内部调用了 resolve 函数后,就会把 promise 实例上的 cbs 全部执行一遍。

到此为止我们还不知道 cbs 这个数组里的函数是从哪里来的,接着往下看。

then

这里是最重要的 then 实现,链式调用全靠它:

    Promise.prototype.then = function (onResolved) {
      // 这里叫做promise2
      return new Promise((resolve) => {
        this.cbs.push(() => {
          const res = onResolved(this.data);
          if (res instanceof Promise) {
            // resolve的权力被交给了user promise
            res.then(resolve);
          } else {
            // 如果是普通值 就直接resolve
            // 依次执行cbs里的函数 并且把值传递给cbs
            resolve(res);
          }
        });
      });
    };

再回到案例里

    const fn = (resolve) => {
      setTimeout(() => {
        resolve(1);
      }, 500);
    };
    
    const promise1 = new Promise(fn);
    
    promise1.then((res) => {
      console.log(res);
      // user promise
      return new Promise((resolve) => {
        setTimeout(() => {
          resolve(2);
        }, 500);
      });
    });

注意这里的命名

  • 我们把 new Promise 返回的实例叫做promise1
  • Promise.prototype.then 的实现中,我们构造了一个新的 promise 返回,叫它promise2
  • 在用户调用 then 方法的时候,用户手动构造了一个 promise 并且返回,用来做异步的操作,叫它user promise
  • 那么在 then 的实现中,内部的 this 其实就指向promise1
  • promise2的传入的fn 函数执行了一个 this.cbs.push(),其实是往 promise1cbs数组中 push了一个函数,等待后续执行
    Promise.prototype.then = function (onResolved) {
      // 这里叫做promise2
      return new Promise((resolve) => {
        // 这里的this其实是promise1
        this.cbs.push(() => {});
      });
    };

那么重点看这个 push 的函数,注意,这个函数在 promise1resolve 了以后才会执行。

    // promise2
    return new Promise((resolve) => {
      this.cbs.push(() => {
        // onResolved就对应then传入的函数
        const res = onResolved(this.data)
        // 例子中的情况 用户自己返回了一个user promise
        if (res instanceof Promise) {
          // user promise的情况
          // 用户会自己决定何时resolve promise2
          // 只有promise2被resolve以后
          // then下面的链式调用函数才会继续执行
          res.then(resolve)
        } else {
          resolve(res)
        }
      })
    })

如果用户传入给 then 的 onResolved 方法返回的是个 user promise,那么这个user promise里用户会自己去在合适的时机 resolve promise2,那么进而这里的 res.then(resolve) 中的 resolve 就会被执行:

    if (res instanceof Promise) {
        res.then(resolve)
    }

结合下面这个例子来看:

    new Promise((resolve) => {
      setTimeout(() => {
        // resolve1
        resolve(1);
      }, 500);
    })
      // then1
      .then((res) => {
        console.log(res);
        // user promise
        return new Promise((resolve) => {
          setTimeout(() => {
            // resolve2
            resolve(2);
          }, 500);
        });
      })
      // then2
      .then(console.log);
  • then1这一整块其实返回的是 promise2,那么 then2 其实本质上是 promise2.then(console.log)
  • 也就是说 then2注册的回调函数,其实进入了promise2的 cbs 回调数组里,又因为我们刚刚知道,resolve2 调用了之后,user promise 会被 resolve,进而触发 promise2resolve,进而 promise2 里的 cbs 数组被依次触发
  • 这样就实现了用户自己写的 resolve2 执行完毕后,then2 里的逻辑才会继续执行,也就是异步链式调用

简单实现一个可以异步链式调用的 promise,而真正的 promise 比它复杂很多很多,涉及到各种异常情况、边界情况的处理。

promise A+规范还是值得每一个合格的前端开发去阅读的

其他版本实现

简易版的Promise:

    const PENDING = 'pending';
    const RESOLVED = 'resolved';
    const REJECTED = 'rejected';
    function MyPromise (fn) {
      let that = this;
      that.status = PENDING;
      that.value = null;
      that.resolvedCallbacks = [];
      that.rejectedCallbacks = [];
      function resolve (value) {
        if (that.status === PENDING) {
          that.status = RESOLVED;
          that.value = value;
          that.resolvedCallbacks.forEach(cb => cb(value))
        }
      }
      function reject (value) {
        if (that.status === PENDING) {
          that.status = REJECTED;
          that.value = value;
          that.rejectedCallbacks.forEach(cb => cb(value))
        }
      }
      try {
        fn(resolve, reject);
      } catch (e) {
        reject(e);
      }
    }
    MyPromise.prototype.then = function (onFulfilled, onRejected) {
      let that = this;
      onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
      onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r };
      if (that.status === PENDING) {
        that.resolvedCallbacks.push(onFulfilled);
        that.rejectedCallbacks.push(onRejected);
      }
      if (that.status === RESOLVED) {
        onFulfilled(that.value)
      }
      if (that.status === REJECTED) {
        onRejected(that.value)
      }
    }
    new MyPromise((resolve, reject) => {
      console.log('我立即执行')
      setTimeout(() => {
        resolve(1)
      }, 1000)
    }).then(res => {
      console.log(res)
    })
Last Updated:
Contributors: leeguooooo