yshaojun / blog

1 stars 1 forks source link

从 es6-prommise 看 Proimse 的实现(一) #8

Open yshaojun opened 4 years ago

yshaojun commented 4 years ago

Promise 是 JS 语言里最大特性之一,通常我们通过 MDN 文档Promises/A+ 规范 去学习它,但前者稍显笼统而后者可读性较差,总感觉有细节没掌握到,于是通过流行的 polyfill 库 es6-promise 来复习一把。

1. Promise

首先看它的构造函数:

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

    if (noop !== resolver) {
      typeof resolver !== 'function' && needsResolver();
      this instanceof Promise ? initializePromise(this, resolver) : needsNew();
    }
  }

定义了 _state 保存 PENDINGFULFILLEDREJECTED 这3个状态,_result 保存结果,_subscribers 保存通过 then 函数添加的回调。

这里不太懂的是为什么需要 PROMISE_ID,搜了下整个代码,只有2处用到,比如 then.js 里:

  const child = new this.constructor(noop);

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

想不到 then 函数用在非 Promise 场景里的意义,因此 then 的上下文应该总是 Promise 实例,其构造函数也应该总是 Promise,所以一定会有 PROMISE_ID 呀?此处欢迎讨论~

然后判断了一下参数 resolver 是不是内部定义的空函数。库内部构造实例时会传入 noop 函数,于是不执行初始化动作;而外部构造时不可能传入 noop,所以一定会走 if 逻辑。

可以看出2点:

  1. 构造 Promise 实例时一定要传入类型为函数的参数,否则报错;
  2. 一定要使用 new 关键字(不同于 jQuery),否则报错。

接下来进入 initializePromise

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

initializePromise 同步 执行了 resolver 函数,所以下面的代码会先输出 111 后输出 222:

new Promise(r => {
  console.log(111) // 先输出
  r()
})
console.log(222) // 后输出

于是运行到了 resolvereject 函数里,先看 resolve

function resolve(promise, value) {
  if (promise === value) {
    reject(promise, selfFulfillment());
  } else if (objectOrFunction(value)) {
    handleMaybeThenable(promise, value, getThen(value));
  } else {
    fulfill(promise, value);
  }
}

判断了3种情况,如果 resolve 的就是当前的 Promise 实例,会报错(仍然想象不到什么场景下会 resolve 当前实例。。);如果是对象或函数,执行 handleMaybeThenable,其他情况执行 fulfill

先看 fulfill

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

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

  if (promise._subscribers.length !== 0) {
    asap(publish, promise);
  }
}

2个作用,一是修改 Promise 状态,这里也能看到“Promise 状态一旦确定(非 PENDING)不能再次修改的原因”,二是通过 asap 异步 执行回调,所以 then 里的函数一定是异步的。

再看 reject,除了修改状态为 REJECTED,其他和 fulfill 基本相同。

一般情况下,new Promise(...) 的所有动作就到此为止了。

回头看 handleMaybeThenable,意思是“如果是非 thenable,则resolve 该对象;如果是 thenable,则用 thenable 最终的状态和结果作为该 Promise 的状态和结果”,比如下面的代码3秒后输出“222 rejected”:

const p = new Promise(r => r(
  new Promise((_, r) => setTimeout(() => r('rejected'), 3000))
))

p.then(
  (...args) => console.log('111', ...args),
  (...args) => console.log('222', ...args)
)

// output(3 seconds later): 222 rejected

2. Promise.prototype.then

这是 Promise 实例最重要的一个方法,Promises/A+ 仅定义了这一个实例函数,其他比如 catchfinally 都可以通过 then 实现。

来看代码:

export default function then(onFulfillment, onRejection) {
  const parent = this;

  const child = new this.constructor(noop);

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

  const { _state } = parent;

  if (_state) {
    const callback = arguments[_state - 1];
    asap(() => invokeCallback(_state, child, callback, parent._result));
  } else {
    subscribe(parent, child, onFulfillment, onRejection);
  }

  return child;
}

先构造了一个新的作为返回值的 Promise 实例 child,然后判断当前 Promise 状态(即 parent),如果是非 PENDING,则异步执行回调 resolve child,否则把 child 及回调放到 parent _subscribers 里:

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

  parent._onerror = null;

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

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

待 parent 执行 fulfillreject 时,该回调被调用。

3. Promise.prototype.catch

实现了 thencatch 就显得格外简单,相当于 then 的第一个参数为 null

catch(onRejection) {
  return this.then(null, onRejection);
}

4. Promise.prototype.finally

finally 也不复杂:

finally(callback) {
  let promise = this;
  let constructor = promise.constructor;

  if ( isFunction(callback) ) {
    return promise.then(value => constructor.resolve(callback()).then(() => value),
                        reason => constructor.resolve(callback()).then(() => { throw reason; }));
  }

  return promise.then(callback, callback);
}

可以看到,finally 执行 callback 后,仍然会 resolve 或者 reject 原值。

Promise 构造及实例方法就到这里,下次继续扒 Promise.allPromise.race 等函数上的方法。