构造函数 Promise
三个状态:
PENDING
,FULFILL
,REJECT
。PID 记录 promise id 属性
_state
当前 promise 的状态(pending/fulfill/reject)_result
当前 promise 任务执行的结果值_subs
当前 promise 的订阅者(then 时注册的 resolver/rejection)构造函数中立即执行 resolver 根据任务执行情况由使用者决定是调用 resolvePromise 还是 rejectPromise
|
|
测试:
|
|
100 resolve
resolve 和 reject 函数
这两个函数至关重要,他们负责改变 promise 的状态值,也是整个 Promise 实现过程中唯 一能改变状态值的地方。
他们的执行会触发所有回调的执行(promise._subs
)。
|
|
asap 函数
|
|
按照 Promise 的定义,被 Promise 定义的任务不论代码是异步的还是同步的,都会被当做 异步任务来执行,比如:
|
|
200 100
结果显示 200 先输出,后输出 100,但是其实我们在 new Promise()
的时候传入的函数
里面其实都是同步代码,经过 Promise 封装置后都成了异步的了。
因此这里的 asap 就是这个作用,当任务就算是同步代码的时候,依然将其变成异步任务去 执行。
并且这里使用了一个队列 queue
来管理这些任务,针对原作者的代码做了一些改动,去
掉了平台有关的代码,并将任务直接二次封装成了一个函数,所以这里是 qlen++
而不是
qlen +=2
。
这里如果不仔细思考可能还不太好理解原作者为什么这么做???
为什么
qlen === 1
的时候触发flush
?答 :其实想明白了也简单,就是为了只要队列是空的时候一旦有新的任务进来就立即触发任务 出列 flush 掉队列中所有的任务,并且是顺序执行,顺序执行,顺序执行,重要的事 情说三遍嘛,想象下如果没有这个机制,一旦有 promise settled 了,就调用一个 setTimeout ?
有了这个机制之后,在 当前的 setTimeout flush 之前,会尽可能的让当前队列承载 更多的 promise 任务,直到 flush 结束,重启另一个 setTimeout。
为什么不直接使用 queue 数组的长度来控制 ?
TODO
情景测试:在 flush 之前 qlen 值发生变化了,需要做点修改让效果更直观。
|
|
测试代码:
|
|
{ qlen: 1 } { qlen: 2 } { qlen: 3 } { qlen: 4 } 100 p1 then 1 100 p2 then 1 100 p3 then 1 100 p4 then 1
看到没, qlen=4
了,因为我们在 asap 中调用 flush 的时候加了 3 秒的延时,所以能
很直观的看得到在一个 settimeout 回调之前会接受到多个 promise 任务。
then 函数实现
then 功能说明:
收集 pending 状态 promise 的 callback(存放到
_subs
中)因为 promise 任务如果异步的,调用
then(resolve,reject)
的时候,resolve 和 reject 是不应该立即执行的,必须等异步任务结束之后再执行,否则就不符合了 promise 原则(异步任务同步化)。所以当 promise 任务是异步情况下,then 函数的功能应该是用来收集 resolve/reject 的,等待任务结束后调用。
作为 then 链式调用的桥梁,即这个桥梁必须是在这个函数里面去完成的。
既然有了收集,那必然就有触发动作,触发也必须等待任务执行完成才会触发,也就是说这
个动作必须是在 resolve()
里面完成,因为 Promise 使用者会根据自己任务情况去在适
当的位置调用 resolve 和 reject。
需要完成的函数:
fulfill(promise, value)
,任务成功完成1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
function fulfill(promise, result) { if (promise._state !== PENDING) { // 状态已经完成不能再改变状态 return; } promise._state = FULFILL; promise._result = result; if (promise._subs.length > 0) { asap(function () { publish(promise); }); } }
publish(promise)
任务完成之后 flush 掉所有回调(then pending 阶段收集的
_subs[]
)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
function publish(promise) { var subs = promise._subs; var child, callback, result = promise.result; for (var i = 0; i < subs.length; i += 3) { child = subs[i]; callback = subs[i + promise._state]; if (child) { // TODO 异步任务 } else { callback(result); } } subs.length = 0; }
subscribe(parent, child, onFulfillment, onRejection)
如果任务是个异步任务就不会立即执行,要等到任务结束才能执行回调,所以就必须要有 个地方能将这些回调收集到当前的 promise 实例中,等待调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
function subscribe(parent, child, onFulfillment, onRejection) { var subs = parent._subs; var len = subs.length; // PENDING subs[len] = child; subs[len + FULFILL] = onFulfillment; subs[len + REJECT] = onRejection; // parent promise 状态如果完成了,立即触发当前 child 的 promise // 可能执行到这里的时候任务刚好完成了??? if (len === 0 && parent._state) { asap(function () { publish(parent); }); } }
then(onFulfillment, onRejection)
这里要区分两种情况,一种是 pending 状态和非 pending 状态的处理,pending 说明可 能是异步任务还没结束,不能立即 settled,调用 subscribe() 去收集回调。
一种是非 pending 状态,在调用 then 之后只有一种情况会使得 promise 状态改变了, 那就是任务立即执行,调用了 resolve 或 reject 设置了
promise._state
改变 了状态,因为只有这两个函数才会改变 promise 状态值。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
function then(onFulfillment, onRejection) { var parent = this; // 创建一个新的 promise,用来衔接后面的 then var child = new this.constructor(noop); var _state = this._state; // 根据状态决定执行哪个回调 var callback = arguments[_state - 1]; if (_state) { // 状态已经改变,任务已经完成了,直接执行回调 invokeCallback(_state, child, callback, parent._result); } else { // 订阅所有回调 subscribe(parent, child, onFulfillment, onRejection); } return child; }
invokeCallback(settled, promise, callback, detail)
这个函数承载了当前 then promise1 的回调执行并解析结果(异常处理),然后将值传递 给下一个 then promise2(then 里面
new this.constructor(noop)
出来的),调用resolve(child)~或 ~reject(child)
去触发 promise2 的回调。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
function invokeCallback(settled, promise, callback, detail) { var value; // 记录 callback 执行的结果 var hasCallback = typeof callback === "function"; var succeeded = true; // callback 可能执行失败 var error; if (hasCallback) { // 开始执行 callback, 即 then(resolve, reject) 的 Resolve/Reject try { // 将上一个 promise 结果作为参数传递到 then 回调 value = callback(detail); } catch (e) { // 回调执行失败,有错误或者异常 error = e; succeeded = false; } if (promise === value) { reject(promise, Util.error.returnSelfPromise()); return; } } else { // 没有回调的时候 then() ??? value = detail; } // 这里要检测下一个新 new 的 promise 状态 // 下面的动作都是为了下一个 then 做准备的,这里的promise // 是在上一个 then 里面的new 出来的 promise 衔接下一个 then 用 if (promise._state !== PENDING) { // noop 状态完成了的 promise } else if (hasCallback && succeeded) { // 执行成功, resolve resolve(promise, value); } else if (succeeded === false) { // then 中的回调执行失败了 reject(promise, error); } else if (settled === FULFILL) { fulfill(promise, value); } else if (settled === REJECT) { reject(promise, value); } }
测试:
|
|
+RESULTS 实现 invokeCallback 之前:
undefined
这里没任何输出,因为还没实现 invokeCallback(settled, promise, callback,
detail)
这里面会针对 then 的 resolve 或 reject 执行结果做出相应的处理。
实现关键点:
callback 实际上是
then(resolve, reject)
中的 resolve/reject ,根据上一个 promise 状态settled
决定的。使用 try…catch 捕获 callback 执行异常,确保 then 回调也能受 Promise 规则约 束。
几种情况决定调用 resolve 还是 reject 进入下一个链式回调(then)。
+RESULTS 实现 invokeCallback 之后:
100 then 1 resolve
此时的 promise._subs 如下:
[ MyPromise { '8st4da5md17': 1, _state: 0, _result: undefined, _subs: [] // 这是那个 child promise }, [Function (anonymous)], // 这里是 then resolver undefined // 这里是 then rejection 因为没传所以是 undefined ]
then 链式调用:
|
|
100 then 1 resolve 200 then 2 resolve
then callback 返回对象或函数
针对返回对象的情况,其实也可以跟普通类型处理一样:
首先要把 Resolve 里面对对象和函数的检测去掉,或者也让它执行 fulfill(promise,
value)
:
|
|
实例:
|
|
100 , then 1 { name: 'then 1 return' } , then 2
但是如果有多个 then 链式调用的情况,一般都会返回一个对象,并且常见情况会是一个异 步的 promise ,这样统一当成普通类型处理就显得不太合理了,这也是为何原作者将返回 值是 函数或对象 的时候分开处理了。
因为在实际使用中,有以下几种场景:
返回纯对象类型(非 promise 或 带有 then 的对象)
返回一个新的 promise 实例
返回一个带有 then 的对象或函数
所以需要做一些特殊处理。
value 可能是 null
捕获这种情况异常执行 reject。
|
|
测试:
|
|
Cannot read property 'then' of null , p then reject 1
value 返回的是一个 Promise
实例
即使用者在 then 回调里面 new 了一个 Promise 实例返回出来,这也是常见的使用场 景之一,经常会有多个有依赖前后结果的异步请求的时候,通过 promise then 来链 式同步执行。
|
|
增加 handleMaybeThenable(promise, value, then);
函数处理其他情况。
|
|
这样,我们就可以处理 then 回调中返回一个 Promise 实例的情况了。
测试:
|
|
100 , p then 1 resolve 200 , p then 2 resolve undefined
所以很顺利的就看到了正确的结果,因为返回的本身就是 MyPromise ,所以只需要根据
其状态做相应的处理即可(fulfill
/ reject
/ subscribe
)。
value 有可能有自己的 then
函数呢?
|
|
测试:
|
|
100 , p then 1 resolve [Function (anonymous)] [Function (anonymous)] object then 200 , p then 2 resolve
如果返回的对象类型属性有一个 then 函数的话,则 MyPromise 的处理是将 resolve 和 reject 封装一层传递给外部的 then ,由它决定何时使用?。
注释 resolve(200)
之后:
100 , p then 1 resolve [Function (anonymous)] [Function (anonymous)] object then
对比下原生的 Promise 呢,将 MyPromise 换成 Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
const { MyPromise, Util} = require(`${process.env.PWD}/../../static/js/promise.js`) const p = new Promise(resolve => { Util.delay(resolve(100)) }).then(val => { console.log(val, ', p then 1 resolve') return { name: 1, then(resolve, reject) { console.log(resolve, reject, 'object then') resolve(200) } } }).then(val => { console.log(val, ', p then 2 resolve') })
100 , p then 1 resolve [Function (anonymous)] [Function (anonymous)] object then 200 , p then 2 resolve结果和 MyPromise 一样,如果在 object then 中不调用 resolve 或 reject 结果, 那么 then 链也会断开:
100 , p then 1 resolve [Function (anonymous)] [Function (anonymous)] object then
疑问 1: foreign then 的 resolve 为什么要判断返回值是不是等于该对象自身?
|
|
答 :如果 value === thenable
,直接调用 resolve(promise, value)
会造成死循
环 resolve -> value.then is function -> handleMaybeThenable ->
handleForeignThenable -> resolve -> …
验证方法,将 asap 延迟时间加大,并且给 asap 套一层,加上延时时间,如下:
|
|
这样可以避免卡死,每隔 500ms 会执行一次 asap,3000ms 之后 flush queue。
修改如下,直接 resolve:
|
|
修改用例:
|
|
执行结果:
➜ js git:(master) ✗ node promise.js 100 , p then 1 resolve 0 , object then 1 , object then 2 , object then 3 , object then 4 , object then 5 , object then 6 , object then // 如果不停止会一直执行下去,因为死循环了
Promise Api 实现
在 ecma262 文档中我们知道 Promise 有如下 APIs:
名称 | 简介 | |
---|---|---|
Promise | all(iterable) | 满足条件:所有 promises 都 fulfilled |
allSettled(iterable) | 不在乎结果是 FULFILL 还是 REJECT,只要所有的任务状态都改变了就 FULFILL 否则 REJECT | |
any(iterable) | 只要有一个任务 FULFILL 结果就是 FULFILL 否则 REJECT | |
race(iterable) | 竞争关系,第一个状态改变发生改变 race 状态就跟着改变,是啥就是啥,FULFILL -> FULFILL, REJECT -> REJECT | |
reject(rejectHandler) | 返回一个必定 REJECT 的 promise | |
resolve(fulfillHandler) | 返回一个必定 FULFILL 的 promise | |
Promise.prototype | catch(onRejected) | |
finally(onFinally) | ||
then(onFulfilled, onRejected) |
当目前表中的函数只实现了 then
和 resolve
下面将一一实现它们。
Promise.reject(rejectHandler)
|
|
test reject result , p reject then
这个跟 Promise.resolve 一样,直接创建一个 Promise 实例 ins,调用 reject(ins,
reason)
|
|
Promise.race(entries)
race 竞争机制,只要有一个 fulfilled 了就立即结束。
|
|
200 , race then reject
只要有一个状态改变了就里面结束 race ,它不在乎结束的时候那个 promise 是 fulfilled 还是 rejected 。
实现:
|
|
Promise.any(entries)
只要有个 promise fulfilled 了,返回的 promise 状态就会变成 fulfilled,否则是 rejected,并且 rejected then 的回调接受的参数 reason 会是 entries 中所有 rejected promise 结果值数组.
修改点:将应该 fulfill()
地方换成 reject()
。
|
|
测试:
|
|
[ 200, 300 ] , any then reject 2 100 , any then resolve 1
第一行输出:p2, p3 都是 REJECT ,所以最后结果是 REJECT 第二行输出:p1, p2 中 p1 是 FULFILL ,所以最后结果是 FULFILL
Promise.all(entries)
all 函数的实现关键:要等到所有的 promise 状态都 settled 了,才能 fulfill。
然后任务有异步也可能有同步任务(即 promise 状态是否立即改变),不管如何都要等到他 们状态发生改变了之后才能让 all 结束,所以两种处理情况
状态立即发生改变了的,直接记录
状态是 PENDING 的注册记录回调直到所有任务都完成
如果遇到有一个 rejected 的立即结束,否则等待所有都 fulfilled 才结束,并且将所 有 fulfilled 结果组成数组传递给下一个 promise。
|
|
原作者的实现通过封装了一个 Enumerator
来实现 Promise.all
其中有一个构造函数
(Enumerator
)和三个原型函数(_settle
, _willSettle
, _enumrate
)。
Enumerator
接受一个 entries 是一个数组构造函数初始化了几个变量:
_length
即 entries 任务的个数_remaining
任务状态是 PENDING 的个数,通过检测这个是不是为零来判断是不是 所有任务都结束了。_result
一个结果数组,保存所有任务执行的结果。
_settle
检测当前 entry 的状态,如果是REJECT
就让this.promise
REJECT ,结束循环,否则保存结果,最后检测this._remaining
。_willSettle
因为 entry 的状态可能是 PENDING 所以要执行订阅,等待它结束再进 入_settle
回到第 2 步。
所以这个函数的实现关键取决于 entry 状态是否是立即改变了,如果是直接检测结果,否
则构造该 entry 的 onFulfillment
和 onRejection
并执行订阅,等待它结束再检测
结果。最后根据结果决定 this.promise
状态,因为 Promise.all
FULFILL 的条件
是所有任务都 FULFILL 才行,所以只要遇到一个 entry rejected 了那么
this.promise
就应该结束且状态是 REJECT 。
测试
|
|
300 , promise all 2 [ 100, 200 ] , promise all 1
改成原生 Promise 结果:
|
|
300 , promise all 2 [ 100, 200 ] , promise all 1
结果 OK。
Promise.allSettled(entries)es2020
这个和 Promise.all
区别在于,它不在乎 entries 中任务的状态是 FULFILL 还是 REJECT ,
只要它状态 settled 即可,且会等到所有任务都 settled 了才会 FULFILL 。
所以这里的实现和 Promise.all
会有所差别,但依然可以通过对 Enumerator
做细微
改动来实现。
修改点:
|
|
测试:
|
|
[ 100, 200 ] , promise allSettled 1 resolve [ 100, 300 ] , promise allSettled 2 resolve
Promise.prototype.finally(onFinally)
这个函数目的就是不管 promise 任务最重是 fulfilled 还是 rejected 都会被执行。
实现如下:
|
|
测试:
|
|
原生 Promise 执行结果
100 then 1 finally 1 undefined then 2 finally 2 a is not defined reject 3
注意这里有几点功能需要完成:
finally 总是要被执行
finally 的参数可以是普通类型的值
finally 调用之后后面还可以继续链式调用,即它执行完也要返回一个 Promise 实例
换成 MyPromise:
|
|
100 then 1 finally 1 undefined then 2 finally 2 a is not defined reject 3
使用 MyPromise 之后又两个异常,被后面两个 then rejection 捕获到了: promise is not defined ,这说明 finally
增加返回值传递给 finally 回调,测试:
|
|
100 then 1 200 , finally 1 undefined
Promise.prototype.catch(onRejection)
26.6.5.1 Promise.prototype.catch ( onRejected ) When the catch method is called with argument onRejected, the following steps are taken: 1. Let promise be the this value. 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
实现:
|
|
测试:
|
|
100 , then 1 a is not defined 200 , then 2
then 2 能够输出是因为 then 1 的错误被 catch 处理掉了,所以 then 2 可以正常 resolve。
完整脑图
Jest 测试
|
|
结果:
➜ js git:(master) ✗ jest jest PASS ./promise.spec.js (5.19s) Promise ✓ Promise then resolve (11ms) ✓ Promise then reject (3ms) ✓ Promise catch (5ms) ✓ Promise finally (4ms) ✓ Promise finally with catch (6ms) ✓ Promise all (205ms) ✓ Promise allSettled (204ms) ✓ Promise race resolve (101ms) ✓ Promise race reject (85ms) ✓ Promise any resolve (102ms) ✓ Promise any reject (85ms) Test Suites: 1 passed, 1 total Tests: 11 passed, 11 total Snapshots: 0 total Time: 9.801s Ran all test suites.
总结
Promise 实现关键点:
resolver 立即执行, resolve/reject 封装之后暴露给 resolver 的使用者调用,比 如:请求成功调用
resolve(value)
, 请求失败调用reject(reason)
。resolve 函数实现需要判断各种情况,主要包含:
不能处理自身,会造成死循环,即 then 的 callback 里面不能返回当前 promise 实 例自身
如果是对象或函数,要考虑到提供了
then(resolve, reject)
函数情况是一个新的 Promise 实例。
任务分立即完成和延时完成情况,立即完成的代表状态立即改变了直接在 then 函数中
fulfill
或reject
即可,如果是异步的,需要通过订阅(subscribe
)来实现。then 函数中为了链式调用,需要新建一个 child promise 作为返回值。
Promise.all
,allSettled
,any
这几个 api 的实现通过 Enumerator 封装。Promise.all
实现原理,遍历所有任务,同步的直接记录结果,异步的订阅结果,异 步完成之后检测最终的值,整个过程中只要中间有任一个 REJECT 了,Promise.all()
状态立即改变为 REJECT 结束,返回结果是: FULFILL-结果数组, REJECT-失败原因。Promise.allSettled
实现原理与Promise.all
大相径庭,只要将Promise.all
里面 遇到 REJECT 的处理改成记录结果就行,最后所有的完成之后就 FULFILL ,返回结果 是所有任务 FULFILL 结果或所有 REJECT 原因组成的数组。Promise.any
的实现与Promise.all
刚好相反,它只要遇到有一个 FULFILL 就结束, 返回 FULFILL 状态,除非所有的都 REJECT 了才会 REJECT ,返回结果值为: FULFILL-成功结果值, REJECT-返回所有失败任务的原因组成的数组。