es6-promise polyfill解读

1,064 阅读4分钟

Promise

详解MDN-Promise polyfill

初始化Promise类

class Promise {
  constructor(resolver) {                
    this[PROMISE_ID] = nextId()
    this._result = this._state = undefined # 1
    this._subscribers = []                 # 2

    if (noop !== resolver) {
      typeof resolver !== 'function' && needResolver() #3
      this instanceof Promise ? initializePromise(this, resolver) : needNew() #4
    }
  }

  finally() {}
  catch() {}
}

Promise.prototype.then = then
export defatult Promise
  • #1 初始化Promise 包含属性result结果,state状态(pending, fullifilled, reject)三种状态不可更改
  • #2 subscibers(用于异步执行时发起订阅),后面会提起
  • #3 调用Promise必须需要传入参数类型为函数
  • #4 调用Promise必须用构造函数方式去用new创建实例

initializePromise

初始化的时候主要就是依靠initializePromise去做一些事情

function initializePromise(promise, resolver) {
  try {
    resolver(function resolvePromise(value){
      resolve(promise, value);
    }, function rejectPromise(reason) {
      reject(promise, reason);
    });
  } catch(e) {
    reject(promise, e);
  }
}

将传入的resolver执行 这个地方一开始让我很绕,其实就是一个高阶函数,执行resolver,并将resolvePromise,rejectPromise两个函数当作参数传入到resolver中供调用者手动去调用。

也就是我们调用的resolve(1)其实调用的是resovlePromise(1),resovlePromise内部又调用函数resolve。

resolve

function resolve(promise, value) {
  if (promise === value) {               #1
    reject(promise, selfFulfillment());
  } else if (objectOrFunction(value)) {  #2
    let then;
    try {
      then = value.then;
    } catch (error) {
      reject(promise, error);
      return;
    }
    handleMaybeThenable(promise, value, then);
  } else {
    // 如果resovle的不是对象或者function直接 fulfill
    fulfill(promise, value);            #3
  }
}
  • #1 resolve调用的使用不能传入parent promise
  • #2 resovle调用的时候如果传入的是对象或者函数,将会检查是否具有then属性。如果then不存在会继续走fulfill处理函数。这里不仔细讨论这种情况。
  • #3 正常如果resolve传入的值不存在then属性且不为promise,将会执行fulfill函数传入value

fulfill

const PENDING = void 0          #1
const FULFILLED = 1
const REJECTED = 2

function fulfill(promise, value) {
  if (promise._state !== PENDING) { return; } #2

  promise._result = value;
  promise._state = FULFILLED;

  if (promise._subscribers.length !== 0) {   #3
    asap(publish, promise); 
  }
}
  • #1 这个地方一开始很纳闷,为什么定义的是void 0 其实是有设计的 void 0 === undefined
  • #2 如果当前promise状态不是初始状态,将改变result为resolve的值,state由初始值变更
  • #3 如果当前promise的subscribers有订阅的消息,将会执行asap函数。

asap函数将会执行订阅的callback,其实就是针对异步的情况。resolve的执行时机决定了fulfill的调用时机。设想如果代码产生了异步执行,一段时间后resolve执行,resolve调用fulfill改变promise的state及result。在此期间new Promise().then()其实在此之前就执行了。只有.then先执行的情况下#3 条件才会成立。

then

function then(onFulfillment, onRejection) {

  // parent 指的就是当前调用的实例对象promise
  const parent = this;

  // child 指代Promise 类
  const child = new this.constructor(noop);

  if (child[PROMISE_ID] === undefined) {
    makePromise(child);
  }

  const { _state } = parent;

  if (_state) {                                  #1
    // 直接返回
    const callback = arguments[_state - 1];
    asap(() => invokeCallback(_state, child, callback, parent._result));                            # 4
  } else {                                       #2
    // 异步执行 
    // 向parent._subscribers 中添加 child onFulfilled onRejection
    subscribe(parent, child, onFulfillment, onRejection);
  }

  return child;                                  # 3
}

执行then函数的时候会内部创建parent 指的就是当前调用的实例对象promise,child 指的就是Promise类。当前promise的初始状态只有上面提的fulfill执行了才会改变。

  • #1 当前promise的状态不为PENDING。此时状态已变,说明同步执行。接着调用asap
  • #2 当前promise的状态为PENDING。此时的状态未被改变,这个时候将会往_subscribers中添加订阅事件。fulfill再执行的时候#3条件将会成立。
  • #3 这就是为什么promise可以链式调用的原因,返回新的promise实例对象
  • #4 asap传入了四个值,_state就是当前promise的状态,child为新创建的实例,callback为then回调函数,最后传入当前promise的_result值

asap

let len = 0
const queue = new Array(1000);

// 检测环境
if (isNode) {
  scheduleFlush = useNextTick();
} else if (BrowserMutationObserver) { // 浏览器环境
  scheduleFlush = useMutationObserver();
} else if (isWorker) {
  scheduleFlush = useMessageChannel();
} else if (browserWindow === undefined && typeof require === 'function') {
  scheduleFlush = attemptVertx();
} else {
  scheduleFlush = useSetTimeout();
}

export var asap = function asap(callback, arg) {
  console.log('implement asap...')
  queue[len] = callback;
  queue[len + 1] = arg;
  len += 2;
  if (len === 2) {
    // If len is 2, that means that we need to schedule an async flush.
    // If additional callbacks are queued before the queue is flushed, they
    // will be processed by this flush that we are scheduling.
    if (customSchedulerFn) {
      customSchedulerFn(flush);
    } else {
      scheduleFlush();                   // 执行
    }
  }
}

asap函数往队列queue中[len]下标中添加回调函数,检测执行环境,目前我们只考虑浏览器的环境,及调用的是useMutationObserver函数

function useMutationObserver() {
  let iterations = 0;
  const observer = new BrowserMutationObserver(flush);
  const node = document.createTextNode('');
  observer.observe(node, { characterData: true });

  return () => {
    node.data = (iterations = ++iterations % 2);
  };
}

function flush() {
  for (let i = 0; i < len; i+=2) {
    let callback = queue[i];
    let arg = queue[i+1];

    callback(arg);

    queue[i] = undefined;
    queue[i+1] = undefined;
  }

  len = 0;
}

useMutationObserver利用了浏览器提供的MutaionObserver方法去创建一个微任务,代调用执行flush。这让我联想到了vue源码中的nextTick的实现。(vue nextTick中是优先使用promise 然后是MutationObservere 之后是Immediate 最后是setTimeout)貌似一个nextTick哈哈...执行flush后将会重置queue队列以及len,并且执行callback。这个callback被invokeCallback包了一层。

invokeCallback

function invokeCallback(settled, promise, callback, detail) {
  let hasCallback = isFunction(callback),
      value, error, succeeded = true;

  if (hasCallback) {
    try {
      value = callback(detail);            #1
    } catch (e) {
      succeeded = false;
      error = e;
    }

    if (promise === value) {
      reject(promise, cannotReturnOwn());
      return;
    }
  } else {
    value = detail;
  }

  // 此时的promise传入的是then函数中定义的child,及新的实例
  if (promise._state !== PENDING) {
    // noop
  } else if (hasCallback && succeeded) {
    resolve(promise, value);       // 再次调用resolve
  } else if (succeeded === false) {
    reject(promise, error);
  } else if (settled === FULFILLED) {
    fulfill(promise, value);
  } else if (settled === REJECTED) {
    reject(promise, value);
  }
}
  • #1 缓存value的值,这一步是为了获取到onFulfillment的值作为return child的_result属性。 场景:Promise().then(() => 1).then(res => console.log(res)) //1

    value = callback(detail)这一步就是真正调用Promise.then(function callback () {})中callback函数的地方。

    至此,then函数中的第一种同步执行情况结束。 第二种异步情况状态还未变更, 执行的subscribe

subscribe

function subscribe(parent, child, onFulfillment, onRejection) {
  let { _subscribers } = parent;           
  let { length } = _subscribers;

  parent._onerror = null;

  _subscribers[length] = child;                     #1
  _subscribers[length + FULFILLED] = onFulfillment;
  _subscribers[length + REJECTED]  = onRejection;

  if (length === 0 && parent._state) {           
    asap(publish, parent);
  }
}

function publish(promise) {
  let subscribers = promise._subscribers;
  let settled = promise._state;

  if (subscribers.length === 0) { return; }

  let child, callback, detail = promise._result;

  for (let i = 0; i < subscribers.length; i += 3) {
    child = subscribers[i];
    callback = subscribers[i + settled];

    if (child) {
      invokeCallback(settled, child, callback, detail);
    } else {
      callback(detail);
    }
  }

  promise._subscribers.length = 0;
}
  • #1 往parent的_subscribers中添加child,订阅事件。何时去发布呢,就是在fulfill中去检测_subscribers的length,如果存在订阅器就去执行asap(publish, promise)。 因为我前面也谈到了 fulfill调用的时机可能在then之后。

随着fulfill的调用,publish的执行会调用asap,asap又会调用publish,publish 又会调用 invokeCallback,风道轮回。其实于同步执行相比 只是多了发布订阅。

异步执行:

first
-> subscribe
async...
-> fulfill -> asap -> publish -> invokeCallback

同步执行:

-> asap -> invokeCallback