Promise A+规范手写

110 阅读5分钟

Promise让我们通过链式调用的方式来解决回调过多的问题,特别是在异步代码中,可以保证我们代码的整洁性和可读性。以Promise A+的规范来从0到1实现promise可以你对它的认知变的很深刻.

Promise-承诺

Promise 代表了一个异步操作的最终结果, Promise的主要交互方式是通过它的then方法, then方法注册回调用于接收Priomse的结果,不管这个结果是最终的值,还是无法实现Promise的原因。

Promise的状态

Promise一共有三种状态

  • pending:初始的状态,在resolve前和reject前处于的状态
  • fulfilled:promise被resolve后处于的状态,这个状态不能够再改变,且拥有一个不可变的值(value)
  • rejected:promise被reject后处于的状态,这个状态不能够再改变,且拥有一个不可变的值(value)
graph TD
pending --> fulfilled
pending --> rejected

所以说promise一旦状态发生转移就不可再转移为其他状态,也就是此过程是不可逆的

const PENDING = 'pending',
      FULFILLED = 'fulfilled',
      REJECTED = 'rejected';

class MyPromise {
  constructor (exector) {
    // promise的初始状态是pending
    this.status = PENDING;
  }
}
A+规范术语
  1. promise: 是拥有一个then方法的对象或函数.
  2. thenable: 是一个定义了 then 方法的对象或函数。这个主要是用来兼容一些老的Promise实现,只要一个Promise实现是thenable,也就是拥有then方法的,就可以跟Promises/A+兼容.
  3. value: 是resolve出来的值,也就是promise成功时的值,可以是任意一个JS中合法的值包括(undefied, thenable和promise等)
  4. exception: 异常,在promise里面用throw抛出来的值
  5. reason: 是reject出来的值,也就是promise拒绝的值reason,表示拒绝的原因
resolve和reject
  • resolve将处于pending状态的promise修改为fulfilled状态
  • reject将处于pending状态的promise修改为rejected状态
  • 并且在构造函数中执行传进来的exector函数
const resolve = (value) => {
  if (this.status === PENDING) {
    this.status = FULFILLED;
    this.value = value;
  }
}

const reject = (reason) => {
  if (this.status === PENDING) {
    this.status = REJECTED;
    this.reason = reason;
  }
}
// 如果有错误(用户在exector中遇到的错误都要reject出去)
try {
  exector(resolve, reject);
} catch(e) {
  reject(e);
}
then

在使用promise的时候如果操作成功就会调用then里面的onFulFilled,如果失败则调用onRejected.所以对应代码中需要检查promise的status,根据stauts来决定走onFulfilled和onRejected

  //then 中的两个函数可缺省
  onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : (val) => val;
  onRejected = typeof onRejected === 'function' ? onRejected : (reason) => reason;

  if (this.status === FULFILLED) {
    onFulFilled(this.value);
  } 

  if (this.status === REJECTED) {
    onRejected(this.reason);
  }

如果我们像这样使用promise,上面的代码就无法处理异步的resolve

  const p = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('asyns resolve')
    }, 1000);
  });

  p.then(res => {
    console.log(res, 'FULFILLED')
  })

当实例化的传参exector函数还没执行完我们就调用了then函数,这时候status的状态还是pending,这时候我们肯定是不能够调用onFulfilled和onRejected,只有在exector函数中主动resolve和reject的时候我们才知道是成功还是失败,所以我们应该在pending的时候把onFulfilled和onRejected利用好数组存起来等exector有了结论再通过resolve和reject来把数组都执行一遍.

class MyPromise {

  constructor (exector) {
    // promise的初始状态是pending
    this.status = PENDING;

    this.value = undefined;
    this.reason = undefined;

    this.onFulFilledCallbacks = [];
    this.onRejectedCallbacks = [];

    const resolve = (value) => {
      if (this.status === PENDING) {
        this.status = FULFILLED;
        this.value = value;

        this.onFulFilledCallbacks.forEach(fn => fn());
      }
    }

    const reject = (reason) => {
      if (this.status === PENDING) {
        this.status = REJECTED;
        this.reason = reason;

        this.onRejectedCallbacks.forEach(fn => fn());
      }
    }
    try {
      exector(resolve, reject);
    } catch(e) {
      reject(e);
    }
  }
  
  then (onFulFilled, onRejected) {

    //then 中的两个函数可缺省
    onFulFilled = typeof onFulFilled === 'function' ? onFulFilled : (val) => val;
    onRejected = typeof onRejected === 'function' ? onRejected : (reason) => reason;

    if (this.status === FULFILLED) {
      onFulFilled(this.value);
    } 

    if (this.status === REJECTED) {
      onRejected(this.reason);
    }

    //如果exector函数还没有resolve和reject
    if (this.status === PENDING) {
      this.onFulFilledCallbacks.push(() => onFulFilled(this.value));
      this.onRejectedCallbacks.push(() => onRejected(this.reason));
    }
  }
}

上面这种先收集回调,等条件满足的时候再拿出来运行的模式即发布订阅,在then中pending状态下push即注册事件,resolve或者reject中执行所有的事件发布。到现在已经完成了异步调用但是还没有完成链式调用.

链式调用
  • 规范中then必须返回一个promise
  • 如果onFullfilled函数返回的是该promise本身,那么会抛出类型错误
  • 如果onFullfilled函数返回的是一个不同的promise,那么执行该promise的then函数,在then函数里将这个promise的状态转移给新的promise III)如果返回的是一个嵌套类型的promsie,那么需要递归。 在规范中还有一条:onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用。这一条的意思是实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。所以在我们执行onFulfilled 和 onRejected的时候都应该包到setTimeout里面去。
then (onFulFilled, onRejected) {
  let promise2 = new MyPromise((resolve, reject) => {

    if (this.status === FULFILLED) {
      try {
        setTimeout(() => {
          let x = onFulFilled(this.value);
          resolvePromise(promise2, x, resolve, reject);
        })
      } catch (e) {
        reject(e);
      }
    }

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

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

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

还有一个最重要的就是在then里面判断onFulfilled和onRejected返回的是否是一个函数或对象或一个promise,对这些场景进行resolve或者reject.

function resolvePromise(promise2, x, resolve, reject) {
  // 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
  // 这是为了防止死循环
  if (promise2 === x) {
    throw new TypeError('chaining cycle');
  }

  // 如果 x 为对象或者函数
  if ((x && typeof x === 'object') || typeof x ==='function') {
    let used = false;
    try {
      let then = x.then;

      // 如果then为函数
      if (typeof then === 'function') {
        then.apply(x, [
          (y) => {
            // 防止多次调用
            if (used) return;
            used = true;
            // y可能还是个promise,所以递归继续解析直到返回一个普通值
            resolvePromise(promise2, y, resolve, reject);
          },
          (r) => {
            if (used) return;
            used = true;
            reject(r);
          }
        ])
      } else {
	// 如果 then 不是函数,以 x 为参数执行 promise
        if (used) return;
        used = true;
        resolve(x);
      }
    } catch(e) {
      if (used) return;
      used = true;		
      reject(e);
    }
  } else {
    // 返回一个普通值
    resolve(x);
  }
}

可以使用promises-aplus-tests的测试用例来测试,使用示例测试通过...