Open yshaojun opened 4 years ago
Promise 是 JS 语言里最大特性之一,通常我们通过 MDN 文档 和 Promises/A+ 规范 去学习它,但前者稍显笼统而后者可读性较差,总感觉有细节没掌握到,于是通过流行的 polyfill 库 es6-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 函数添加的回调。
_state
PENDING
FULFILLED
REJECTED
_result
_subscribers
then
这里不太懂的是为什么需要 PROMISE_ID,搜了下整个代码,只有2处用到,比如 then.js 里:
PROMISE_ID
const child = new this.constructor(noop); if (child[PROMISE_ID] === undefined) { makePromise(child); }
想不到 then 函数用在非 Promise 场景里的意义,因此 then 的上下文应该总是 Promise 实例,其构造函数也应该总是 Promise,所以一定会有 PROMISE_ID 呀?此处欢迎讨论~
然后判断了一下参数 resolver 是不是内部定义的空函数。库内部构造实例时会传入 noop 函数,于是不执行初始化动作;而外部构造时不可能传入 noop,所以一定会走 if 逻辑。
resolver
noop
可以看出2点:
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); } }
initializePromise 同步 执行了 resolver 函数,所以下面的代码会先输出 111 后输出 222:
new Promise(r => { console.log(111) // 先输出 r() }) console.log(222) // 后输出
于是运行到了 resolve 和 reject 函数里,先看 resolve:
resolve
reject
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。
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 里的函数一定是异步的。
asap
再看 reject,除了修改状态为 REJECTED,其他和 fulfill 基本相同。
一般情况下,new Promise(...) 的所有动作就到此为止了。
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
这是 Promise 实例最重要的一个方法,Promises/A+ 仅定义了这一个实例函数,其他比如 catch、finally 都可以通过 then 实现。
catch
finally
来看代码:
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 里:
child
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 时,该回调被调用。
实现了 then,catch 就显得格外简单,相当于 then 的第一个参数为 null:
null
catch(onRejection) { return this.then(null, onRejection); }
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 等函数上的方法。
Promise.all
Promise.race
Promise 是 JS 语言里最大特性之一,通常我们通过 MDN 文档 和 Promises/A+ 规范 去学习它,但前者稍显笼统而后者可读性较差,总感觉有细节没掌握到,于是通过流行的 polyfill 库 es6-promise 来复习一把。
1. Promise
首先看它的构造函数:
定义了
_state
保存PENDING
、FULFILLED
、REJECTED
这3个状态,_result
保存结果,_subscribers
保存通过then
函数添加的回调。这里不太懂的是为什么需要
PROMISE_ID
,搜了下整个代码,只有2处用到,比如 then.js 里:想不到
then
函数用在非 Promise 场景里的意义,因此then
的上下文应该总是 Promise 实例,其构造函数也应该总是 Promise,所以一定会有PROMISE_ID
呀?此处欢迎讨论~然后判断了一下参数
resolver
是不是内部定义的空函数。库内部构造实例时会传入noop
函数,于是不执行初始化动作;而外部构造时不可能传入noop
,所以一定会走 if 逻辑。可以看出2点:
new
关键字(不同于 jQuery),否则报错。接下来进入
initializePromise
:initializePromise
同步 执行了resolver
函数,所以下面的代码会先输出 111 后输出 222:于是运行到了
resolve
和reject
函数里,先看resolve
:判断了3种情况,如果
resolve
的就是当前的 Promise 实例,会报错(仍然想象不到什么场景下会 resolve 当前实例。。);如果是对象或函数,执行handleMaybeThenable
,其他情况执行fulfill
。先看
fulfill
:2个作用,一是修改 Promise 状态,这里也能看到“Promise 状态一旦确定(非 PENDING)不能再次修改的原因”,二是通过
asap
异步 执行回调,所以then
里的函数一定是异步的。再看
reject
,除了修改状态为REJECTED
,其他和fulfill
基本相同。一般情况下,
new Promise(...)
的所有动作就到此为止了。回头看
handleMaybeThenable
,意思是“如果是非 thenable,则resolve 该对象;如果是 thenable,则用 thenable 最终的状态和结果作为该 Promise 的状态和结果”,比如下面的代码3秒后输出“222 rejected”:2. Promise.prototype.then
这是 Promise 实例最重要的一个方法,Promises/A+ 仅定义了这一个实例函数,其他比如
catch
、finally
都可以通过then
实现。来看代码:
先构造了一个新的作为返回值的 Promise 实例
child
,然后判断当前 Promise 状态(即 parent),如果是非PENDING
,则异步执行回调 resolvechild
,否则把 child 及回调放到 parent_subscribers
里:待 parent 执行
fulfill
或reject
时,该回调被调用。3. Promise.prototype.catch
实现了
then
,catch
就显得格外简单,相当于then
的第一个参数为null
:4. Promise.prototype.finally
finally
也不复杂:可以看到,
finally
执行 callback 后,仍然会 resolve 或者 reject 原值。Promise 构造及实例方法就到这里,下次继续扒
Promise.all
、Promise.race
等函数上的方法。