Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

根据Promise/A+规范实现 Promise #42

Open
amandakelake opened this issue Apr 6, 2018 · 4 comments
Open

根据Promise/A+规范实现 Promise #42

amandakelake opened this issue Apr 6, 2018 · 4 comments

Comments

@amandakelake
Copy link
Owner

amandakelake commented Apr 6, 2018

Promises/A+

整篇promise的实现不是我原创,是我研究了群里某位大佬以及一些前人的实现,在自己理解的基础上,记录下来,本篇文章更多是为自己服务

注释我写成英文了(具体缘由暂时不说了)
相信有能力理解promise的程序员都有阅读英文的能力(暂时没有也建议掌握这项能力)

// three states
const PENGING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

// promise accepts a function argument that will execute immediately.
function MyPromise(fn) {
  const _this = this;
  _this.currentState = PENDING;
  // the value of Promise
  _this.value = undefined;
  // To save the callback of `then`,only cached when the state of the promise is pending,
  // for the new instance returned by `then`, at most one will be cached
  _this.resolvedCallbacks = [];
  _this.rejectedCallbacks = [];

  _this.resolve = (value) => {
    if (value instanceof MyPromise)
      // If value is a Promise, execute recursively
      return value.then(_this.resolve, _this.reject)
    }
    // execute asynchronously to guarantee the execution order
    setTimeout(() => {
      if (_this.currentState === PENDING) {
        _this.currentState = RESOLVED;
        _this.value = value;
        _this.resolvedCallbacks.forEach(cb => cb());
      }
    })    
  }

  _this.reject = (reason) => {
    setTimeout(() => {
      if (_this.currentState === PENGING) {
        _this.currentState = REJECTED;
        _this.value = reason;
        _this.rejectedCallbacks.forEach(cb => cb());
      }
    })
  }

  // to solve the following problem
  // new Promise(() => throw Error('error))
  try {
    fn(_this.resolve, _this.reject);
  } catch (e) {
    _this.reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  const self = this;
  // specification 2.2.7, `then` must return a new promise
  let promise2;
  // specification 2.2, both `onResolved` and `onRejected` are optional arguments
  // it should be ignored if `onResolved` or `onRjected` is not a function, which implements the penetrate pass of it's value
  // Promise.resolve(4).then().then((value) => console.log(value))
  onResolved = typeof onResolved === 'function' ? onResolved : v => v;
  onRejected = typeof onRejected === 'function' ? onRejected : r => throw r;

  if (self.currentState === RESOLVED) {
    return (promise2 = new MyPromise((resolve, reject) => {
      // specification 2.2.4, wrap them with `setTimeout`, in order to insure that `onFulfilled` and `onRjected` execute asynchronously, 
      setTimeout(() => {
        try {
          let x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }));
  }

  if (self.currentState === REJECTED) {
    return (promise2 = new MyPromise((resolve, reject) => {
      // execute `onRejected` asynchronously
      setTimeout(() => {
        try {
          let x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (reason) {
          reject(reason);
        }
      });
    }))
  }

  if (self.currentState === PENDING) {
    return (promise2 = new MyPromise((resolve, reject) => {
      self.resolvedCallbacks.push(() => {
         // Considering that it may throw error, wrap them with `try/catch`
        try {
          let x = onResolved(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      });

      self.rejectedCallbacks.push(() => {
        try {
          let x = onRejected(self.value);
          resolutionProcedure(promise2, x, resolve, reject);
        } catch (r) {
          reject(r);
        }
      })
    }))
  }
}

// specification 2.3
function resolutionProcedure(promise2, x, resolve, reject) {
  // specification 2.3.1,`x` and  `promise2` can't refer to the same object, avoiding the circular references
  if (promise2 === x) {
    return reject(new TypeError('Error'));
  }

  // specification 2.3.2, if `x` is a Promise and the state is `pending`, the promise must remain, If not, it should execute. 
  if (x instanceof MyPromise) {
    if (x.currentState === PENDING) {
      // call the function `resolutionProcedure` again to confirm the type of the argument that x resolves
      // If it's a primitive type, it will be resolved again to pass the value to next `then`.
      x.then((value) => {
        resolutionProcedure(promise2, value, resolve, reject);
      }, reject)
    } else {
      x.then(resolve, reject);
    }
    return;
  }

  // specification 2.3.3.3.3 
  // if both `reject` and `resolve` are executed, the first successful execution takes precedence, and any further executions are ignored
  let called = false;
  // specification 2.3.3, determine whether `x` is an object or a function
  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
    // specification 2.3.3.2, if can't get `then`, execute the `reject`
    try {
      // specification 2.3.3.1
      let then = x.then;
      // if `then` is a function, call the `x.then`
      if (typeof then === 'function') {
        // specification 2.3.3.3
        then.call(x, y => {
          if (called) return;
          called = true;
          // specification 2.3.3.3.1
          resolutionProcedure(promise2, y, resolve, reject);
        }, e => {
          if (called) return;
          called = true;
          reject(e);
        });
      } else {
        // specification 2.3.3.4
        resolve(x);
      }
    } catch (e) {
      if (called) return;
      called = true;
      reject(e);
    }
  } else {
    // specification 2.3.4, `x` belongs to primitive data type
    resolve(x);
  }
}
@julytang
Copy link

julytang commented Jun 15, 2018

你已经使用箭头函数了,没必要用_this指向this了
You have already used the arrow function, so it's unnecessary to define _this

@amandakelake
Copy link
Owner Author

@julytang 谢谢指正
我原来很多地方是用ES5语法写的 所以为了保险才加上去的 现在的确可以去掉了

@julytang
Copy link

@amandakelake 博客很棒,整理的很完整,点赞,加油

@amandakelake
Copy link
Owner Author

@julytang 谢谢鼓励
路漫漫其修远兮 还有很多要学的

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants