theydy / notebook

记录读书笔记 + 知识整理,vuepress 迁移中 https://theydy.github.io/notebook/
0 stars 0 forks source link

Promise 实现 + 注释 #10

Open theydy opened 5 years ago

theydy commented 5 years ago
const Promise = (function () {

  function MyPromise(fn) {
    const self = this;
    self.state = 'pending';
    self.value = undefined;
    self.callbacks = [];

    function resolve(value) {
      setTimeout(() => {
        if (self.state === 'pending') {
          self.state = 'resolved';
          self.value = value;
          self.callbacks.map(cb => {
            cb.onResolved(value);
          });
        }
      }, 0);
    }

    function reject(value) {
      setTimeout(() => {
        if (self.state === 'pending') {
          self.state = 'rejected';
          self.value = value;
          self.callbacks.map(cb => {
            cb.onRejected(value);
          });
        }
      }, 0);
    }

    try {
      fn(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  const resolvePromise = function (promise, x, resolve, reject) {
    let called = false;
    let then;

    /**
     * 检测循环引用
     */
    if (promise === x) {
      return reject(new TypeError('Error'));
    }

    if (x instanceof MyPromise) {
      return x.then(y => {
        resolvePromise(promise, y, resolve, reject);
      }, reject);
    }

    if (
      /**
       * x !== null 不能省略,因为 `typeof null` 输出的是 "object",所以要排除 null。
       */
      (x !== null) &&
      (
        (typeof x === 'object') ||
        (typeof x === 'function')
      )
    ) {
      try {
        then = x.then;
        if (typeof then === 'function') {
          then.call(x,
          y => {
            if (called) return;
            called = true;
            resolvePromise(promise, y, resolve, reject);
          },
          r => {
            if (called) return;
            called = true;
            reject(r);
          });
        } else {
          resolve(x);
        }
      } catch (e) {
        if (called) return
        called = true;
        reject(e);
      }
    } else {
      resolve(x);
    }
  }

  MyPromise.prototype.then = function (onResolved, onRejected) {
    const self = this;
    let promise2;

    /**
     * onResolved 和 onRejected 的默认值处理数据传递的问题
     */
    onResolved = typeof onResolved === 'function' ? onResolved : v => v;
    onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r };

    if (self.state === 'pending') {
      return (promise2 = new MyPromise((resolve, reject) => {
        self.callbacks.push({
          onResolved: () => {
            try {
              const x = onResolved(self.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          },
          onRejected: () => {
            try {
              const x = onRejected(self.value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (e) {
              reject(e);
            }
          }
        });
      }))
    }

    if (self.state === 'resolved') {
      /**
       * 为了能够链式调用promise,所以then 方法最后要返回一个新promise。
       * new promise 时传的函数立即执行。然后调用resolve 或 reject 改变状态成 resolved 或 rejected。
       * 所以要注意的是在这个promise 的同步代码中根据情况调用 resolve 或 reject,
       * 正确的改变这个promise 的状态。
       */
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            const x = onResolved(self.value);
            resolvePromise(promise2, x, resolve, reject);
            /**
             * 如上面所说,新promise 中的同步代码的作用就是执行 上一个promise 的onResolved 或onRejected ,
             * 并根据返回的结果,正确的改变新promise 的状态。
             * 所以resolvePromise 这个函数就是先判断了一大堆情况,最后执行了 resolve(value) 或 reject(value)。
             * 把这个新promise 的状态给改变了。
             */
          } catch (e) {
            reject(e);
          }
        }, 0);
      }));
    }

    if (self.state === 'rejected') {
      return (promise2 = new MyPromise((resolve, reject) => {
        setTimeout(() => {
          try {
            const x = onRejected(self.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }));
    }
  }

  MyPromise.prototype.catch = function (onRejected) {
    return this.then(null, onRejected);
  }

  MyPromise.prototype.finally = function (fn) {
    return this.then(fn, fn);
  }

  MyPromise.all = function (promises) {
    return new MyPromise((resolve, reject) => {
      let count = 0;
      let nums = promises.length;
      let values = new Array(nums);

      for (let i = 0; i < nums; i++) {
        Promise.resolve(promises[i]).then(value => {
          count++;
          values[i] = value;
          if (count === nums) {
            return resolve(values);
          }
        }, r => {
          return reject(r);
        })
      }
    })
  }

  MyPromise.race = function (promises) {
    return new MyPromise((resolve, reject) => {
      let nums = promises.length;
      for (let i = 0; i < nums; i++) {
        MyPromise.resolve(promises[i]).then(value => {
          return resolve(value);
        }, r => {
          return reject(r);
        })
      }
    })
  }

  MyPromise.resolve = function (value) {
    let promise = new MyPromise((resolve, reject) => {
      resolvePromise(promise, value, resolve, reject)
    })
    return promise;
  }

  MyPromise.reject = function (value) {
    let promise = new MyPromise((resolve, reject) => {
      reject(value);
    })
    return promise;
  }  

  // 使用 promises-aplus-tests 测试
  MyPromise.deferred = MyPromise.defer = function () {
    var dfd = {};
    dfd.promise = new MyPromise(function (resolve, reject) {
      dfd.resolve = resolve;
      dfd.reject = reject;
    })
    return dfd;
  }

  try {
    module.exports = MyPromise;
  } catch (e) { }
  // 使用 promises-aplus-tests 测试 end

  return MyPromise;
})();
theydy commented 3 years ago

简化版


function MiniPromise(fn) {
  this.cbs = [];
  this.value = '';
  this.state = 'pending';

  const resolve = (data) => {
    setTimeout(() => {
      this.state = 'resolved';
      this.value = data;
      this.cbs.map(cb => cb.onResolved(value));
    }, 0);
  }

  fn(resolve);
}

MiniPromise.prototype.then = function (onResolved, onRejected) {
  return new MiniPromise((resolve, reject) => {
    this.cbs.push({
      onResolved: (value) => {
        const res = onResolved(value);
        if (res instanceof MiniPromise) {
          res.then(resolve);
        } else {
          resolve(res);
        }
      },
    })
  })
}