一个符合Promise/A+的Promise实现

  • 2018-10-31
  • 70

代码主要来自大佬工业聚

在其基础上:
– 增加了存在未处理的reject的警告
– 通过Symbol“隐藏”value及state

const PENDING = 'pending';
const RESOLVED = 'resolved';
const REJECTED = 'rejected';

const stateSymbol = Symbol('state');
const valueSymbol = Symbol('value');
const innerPropsSymbol = Symbol('innerProps');

const isFunction = val => typeof val === 'function';
const isObject = val => val && typeof val === 'object';
const isThenable = val => (isFunction(val) || isObject(val)) && 'then' in val;
const nextTick = fn => setTimeout(fn);

function handleTasks(ctx) {
  const {
    [innerPropsSymbol]: { tasks },
    [stateSymbol]: state,
    [valueSymbol]: value,
  } = ctx;
  nextTick(() => {
    if (state === REJECTED) {
      if (ctx[innerPropsSymbol].haveUnhandleReject && tasks.length === 0) {
        console.error('未处理reject');
      } else {
        ctx[innerPropsSymbol].haveUnhandleReject = false;
      }
    }
    while (tasks.length) {
      handleTask(tasks.shift(), state, value);
    }
  });
}

function handleTask(task, state, value) {
  const { onFulfilled, onRejected, resolve, reject } = task;
  try {
    if (state === RESOLVED) {
      isFunction(onFulfilled) ? resolve(onFulfilled(value)) : resolve(value);
    } else if (state === REJECTED) {
      isFunction(onRejected) ? resolve(onRejected(value)) : reject(value);
    }
  } catch (err) {
    reject(err);
  }
}

function MyPromise(fn) {
  if (!this instanceof MyPromise) throw new Error('只能用于构造函数');
  if (!isFunction(fn)) throw new TypeError('MyPromise的参数只能是函数');

  this[stateSymbol] = PENDING;
  this[innerPropsSymbol] = {
    tasks: [],
    haveUnhandleReject: true,
  };
  this[valueSymbol] = undefined;

  const transValue = (state, value) => {
    if (this[stateSymbol] !== PENDING) return;
    this[valueSymbol] = value;
    this[stateSymbol] = state;
    handleTasks(this);
  };

  const onFulfilled = value => transValue(RESOLVED, value);
  const onRejected = reason => transValue(REJECTED, reason);
  // 由于resolve的过程可能存在异步改变状态的情况
  // 所以,需要确保resolve或reject只能有一个被调用,且只调用一次
  let ignor = false;
  const resolve = value => {
    if (ignor) return;
    ignor = true;
    if (value === this) {
      onRejected(new TypeError('promise循环调用错误'));
    } else if (value instanceof MyPromise) {
      value.then(onFulfilled, onRejected);
    } else if (isThenable(value)) {
      try {
        const then = value.then;
        if (isFunction(then)) {
          new MyPromise(then.bind(value)).then(onFulfilled, onRejected);
        } else {
          onFulfilled(value);
        }
      } catch (err) {
        onRejected(err);
      }
    } else {
      onFulfilled(value);
    }
  };

  const reject = reason => {
    if (ignor) return;
    ignor = true;
    onRejected(reason);
  };

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

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  return new MyPromise((resolve, reject) => {
    this[innerPropsSymbol].tasks.push({
      onFulfilled,
      onRejected,
      resolve,
      reject,
    });
    // new Promise(resolve=>resolve()).then(fn1,fn2) 过程中
    // 状态改变在前,push task在后,所以,这里也触发一次任务队列处理
    // 或者为已经不在pending状态的promise添加then,也需要触发任务处理
    if (this[stateSymbol] !== PENDING) {
      handleTasks(this);
    }
  });
};

MyPromise.resolve = function(value) {
  return new MyPromise(resolve => resolve(value));
};

MyPromise.reject = function(reason) {
  return new MyPromise((_, reject) => reject(reason));
};