从 es6-prommise 看 Proimse 的实现(一)
yshaojun opened this issue · comments
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
保存 PENDING
、FULFILLED
、REJECTED
这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点:
- 构造 Promise 实例时一定要传入类型为函数的参数,否则报错;
- 一定要使用
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) // 后输出
于是运行到了 resolve
和 reject
函数里,先看 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+ 仅定义了这一个实例函数,其他比如 catch
、finally
都可以通过 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 执行 fulfill
或 reject
时,该回调被调用。
3. Promise.prototype.catch
实现了 then
,catch
就显得格外简单,相当于 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.all
、Promise.race
等函数上的方法。