es6-promise阅读

259 阅读5分钟

从Promise类开始

  • 创建一个Promise需要传递一个函数参数,函数参数是同步执行的,函数参数可以接收到两个函数作为函数参数的参数。调用两个函数中的一个就会确定其真正状态,初始默认为pending状态,状态一旦改变就不可变。
function noop() {}
const PENDING   = void 0;
const FULFILLED = 1;
const REJECTED  = 2;

class Promise {
  constructor(resolver) {
    this._result = this._state = PENDING
    this._subs = []
    
    if (noop !== resolver) { // noop的作用是标志是否为then方法创建的,then方法自己需要返回一个promise且没有resolver
      typeof resolver !== 'function' && needResolver() // 不是函数则报错提示需要传入函数
      this instanceof Promise ? initializePromise(this, resolver) : needNew() // 必须是new调用
    }
  }
  catch(onRejection) { // catch是语法糖
    return this.then(null, onRejection)
  }
  finally(cb) {
    let promise = this
    let constructor = promise.constructor
    
    if (typeof cb === 'function') { // 只有在同一个函数里才能拿到其函数参数,故这里调用cb的时候需要使用Promise.resolve包裹返回值,这样就能在其后面调用then方法拿到函数参数
    // 如果cb返回一个Promise.reject,则可以原封不动将其reject的reason往下传递
      return promise.then(value => constructor.resolve(cb()).then(() => value),
      reason => constructor.resolve(cb()).then(() => { throw reason }))
    }
    return promise.then(cb, cb)
  }
}

function initializePromise(promise, resolver) {
  try {
    resolver(function resolvePromise(value){
      resolve(promise, value);
    }, function rejectPromise(reason) {
      reject(promise, reason);
    });
  } catch(e) {
    reject(promise, e);
  }
}
  • 如果校验都通过了,那么执行resolver并传递两个函数,如果执行报错则用reject。
  • finally做一些收尾工作,无论当前promsise的状态是什么都会执行,并将promise的值传递给下一个then方法,由于then方法执行函数,其返回值会覆盖前promise的值,为了传递promise的值需要再次使用then方法传递。

不同状态下执行的方法

resolve

function resolve(promise, value) {
  if (promise === value) { // 不能resolve 本身
    reject(promise, selfFulfillment());
  } else if (objectOrFunction(value)) {
    let then;
    try {
      then = value.then;
    } catch (error) {
      reject(promise, error);
      return;
    }
    handleMaybeThenable(promise, value, then); // 可能存在then方法
  } else {
    fulfill(promise, value);
  }
}

function handleMaybeThenable(promise, maybeThenable, then) {
  if (maybeThenable.constructor === promise.constructor &&
      then === originalThen &&
      maybeThenable.constructor.resolve === originalResolve) { // 如果返回promise
    handleOwnThenable(promise, maybeThenable);
  } else {
    if (then === undefined) {
      fulfill(promise, maybeThenable);
    } else if (isFunction(then)) {
      handleForeignThenable(promise, maybeThenable, then);
    } else {
      fulfill(promise, maybeThenable);
    }
  }
}

function handleOwnThenable(promise, thenable) {
  if (thenable._state === FULFILLED) {
    fulfill(promise, thenable._result);
  } else if (thenable._state === REJECTED) {
    reject(promise, thenable._result);
  } else {
    subscribe(thenable, undefined, value  => resolve(promise, value),
                                   reason => reject(promise, reason))
  }
}

function fulfill(promise, value) {
  if (promise._state !== PENDING) { return; } // 一旦落定就不可以改变

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

  if (promise._subscribers.length !== 0) { // 改变状态后开始执行subs中的对应状态回调
    asap(publish, promise);
  }
}
  • 执行用户传入的函数,并传入两个函数作为参数,两个函数执行后分别调用resolve和reject。
  • resolve主要是处理传入的value是否有then方法,fulfill方法才是去改变promise状态。如果value存在then方法,then返回值状态落定,则修改promise状态为fulfill(无论then是reject还是fulfill),如果then返回值待定,则需要为then返回值添加回调,等待落定后调用回调。

reject

function reject(promise, reason) {
  if (promise._state !== PENDING) { return; }
  promise._state = REJECTED;
  promise._result = reason;

  asap(publishRejection, promise);
}
  • reject没有对then进行解构拿到其value。

状态未定使用订阅函数

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

  parent._onerror = null;

  _subs[length] = child;
  _subs[length + FULFILLED] = onFulfillment;
  _subs[length + REJECTED]  = onRejection;

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

function publish(promise) {
  let subs = promise._subs;
  let settled = promise._state;

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

  let child, callback, detail = promise._result;

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

    if (child) {
      invokeCallback(settled, child, callback, detail);
    } else {
      callback(detail); // 对应上面resolve被传递了一个带有then方法且then的结果未定
    }
  }

  promise._subs.length = 0;
}

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

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

    if (promise === value) {
      reject(promise, cannotReturnOwn());
      return;
    }
  } else {
    value = detail; // 没有函数则将值顺着往下传递
  }

  if (promise._state !== PENDING) { // 过滤状态已经待定的
    // noop
  } else if (hasCallback && succeeded) { // value可能是thenable,需要交给resolve处理
    resolve(promise, value);
  } else if (succeeded === false) {
    reject(promise, error);
  } else if (settled === FULFILLED) { // 说明cb不是函数,则promise取决父promise值,即将值顺延下去
    fulfill(promise, value);
  } else if (settled === REJECTED) {
    reject(promise, value);
  }
}

接下来看看then

Promise.prototype.then = then

function then(onFulfillment, onRejection) {
  const parent = this
  const child = new this.constructor(noop)
  
  const { _state } = parent
  if (_state) {
    const cb = arguments[_state - 1] // 获得对应的fullfill cb 或 reject cb
    asap(() => invokeCallback(_state, child, callback, parent._result))
  } else {
    subscribe(parent, child, onFulfillment, onRejection)
  }
  
  return child
}
  • 如果状态已经确定就直接开启一个微/宏任务执行,否则加入promise的数组中

assp中的异步队列

const queue = [];
let len = 0;
let scheduleFlush;

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;
}

if (isNode) {
  scheduleFlush = () => process.nextTick(flush);
} else if (BrowserMutationObserver) {
  scheduleFlush = (function() {
    let iterations = 0;
    const observer = new BrowserMutationObserver(flush);
    const node = document.createTextNode('');
    observer.observe(node, { characterData: true });

    return () => {
    node.data = (iterations = ++iterations % 2);
    };
  })();
} else if (isWorker) { // web worker
  scheduleFlush = (
    function useMessageChannel() {
      const channel = new MessageChannel();
      channel.port1.onmessage = flush;
      return () => channel.port2.postMessage(0);
    }
  )();
} else {
  scheduleFlush = (
    useSetTimeout() {
      // Store setTimeout reference so es6-promise will be unaffected by
      // other code modifying setTimeout (like sinon.useFakeTimers())
      const globalSetTimeout = setTimeout;
      return () => globalSetTimeout(flush, 0);
    }
  )();
}

function asap(callback, arg) {
  queue[len] = callback;
  queue[len + 1] = arg;
  len += 2;
  if (len === 2) { // 说明state已经落定且是刚开始触发,后续往队列里面添加数据即可,不需要触发执行调度函数
    scheduleFlush();
  }
}
  • 同步任务优先于微任务和宏任务,即使调用了调度函数也需要等同步任务执行完才能执行flush函数,故在同步阶段仍然可以往队列中添加回调函数和promise参数。

其他几个静态方法

race

function race(entries) {
  let Constructor = this;

  if (!isArray(entries)) {
    return new Constructor((_, reject) => reject(new TypeError('You must pass an array to race.')));
  } else {
    return new Constructor((resolve, reject) => {
      let length = entries.length;
      for (let i = 0; i < length; i++) {
        Constructor.resolve(entries[i]).then(resolve, reject);
      }
    });
  }
}

all

function all(entries) {
  return new Promise((resolve, reject) => {
    let cnt = 0, len = entries.length, result = []
    entries.forEach((item, idx) => {
      Promise.resolve(item).then((value) => {
        ++cnt
        result[idx] = value
        if (cnt === len) resolve(result)
      }, (reason) => {
        ++cnt
        reject(reason)
      })
    })
  })
}

allSettled

function allSettled(entries) {
  return new Promise((resolve) => {
    let cnt = 0, len = entries.length, result = []
    entries.forEach((item, idx) => {
      Promise.resolve(item).then((value) => {
        ++cnt
        result[idx] = { status: 'fulfilled', value }
        if (cnt === len) resolve(result)
      }, (reason) => {
        ++cnt
        result[idx] = { status: 'rejected', reason }
        if (cnt === len) resolve(result)
      })
    })
  })
}