初学 Promise 时就让我很好奇一点,Promise 是如何将异步转化为同步语法进行执行的?
观察者模式
Vue 的设计指导模式就是发布-订阅模式,而发布订阅算是观察者模式的衍生产物。
观察者模式:发布者和观察者共同维护一个列表,当列表内容发生变化时会以广播的形式通知给每一个观察者
发布订阅模式:发布者维护多个列表,而订阅者可以根据自身需要进行特定属性的订阅
1 | /* |
上述的发布订阅就是顺嘴一提,主角还是观察者模式。
在大家学习的时候不知有没有好奇,Promise 是如何得知异步事件执行完毕的呢?
先想想你平时的使用方式,以 ajax 为例
1 | const $ajax = param => { |
其中 then 就是观察者,一旦状态改变则将控制权交由其订阅者。如此一来我们就有了如下对应关系:
- resolve / reject —— notifyAll
- subscribe —— then
改版为Promise
有了上述设计模式的支持我们可以尝试手写简易版本,目前我们已知的几个属性如下:
- 一旦创建会立即执行
- 一旦开始则无法停止且只执行一次
- 接收一个成功后的 function
- 状态改变后无法回退
- then 对应我们的观察者 subscribe
- resolve / reject 对应我们的发布者 notifyAll
然后魔改一下上面的观察者模式代码:
1 | const STATUS = { |
ok那我们第一步已经完成,魔改成功。
如果你打开 chrome 断点调试你会发现, 最终的结果打印经历了这么几个步骤:
- 初始化 resolve 函数
- 初始化监听列表 & 状态(防止执行多次)
- 异步操作(定时任务)
- 将结果传入 resolve
- 改变状态,清空(执行)监听列表
那么是什么时候执行的 push 操作呢?
push callback
由于情况较为特殊,断点并未显示执行 then 的过程,所以我们在 push 操作前可以打印一句话作为参考坐标
console.log(observer)
而其执行位置则是在异步操作执行结束后,也就是断点刚刚到 setTimeout 内部回调【resolve(‘hello world’)】时执行了push 操作。
故完整过程如下
- 初始化【resolve、监听列表、状态】
- 异步操作
- push callback(then中的回调) to list
- 结果传入 resolve
- 改变状态,清空(执行)监听列表
完善Promise
上边的内容我们仅仅是作为观察者模式的延展,根据 Promise/A+ 规范,真正的 promise 我们还需要考虑以下几点
- 异步执行失败的状态
- 链式调用
状态
首先就是加上 reject 状态及 value
1 | const STATUS = { |
但此时有了不同的状态(成功 & 失败)后,还要对之前的then做一下处理,以适应。链式调用我们就要分情况讨论了
调用
promise 既然接受两个状态回调,那么我们自然也需要更改一下构造器里的list,将其分为监听 resolve 的以及接收 reject 的两种不同状态对应的回调函数。
1 | const STATUS = { |
链式调用
首先,Promise 链式调用的原理即是将结果包装为一个新的 Promise,那么我们就必须要保证每次返回的均为一个新的 Promise(调用then的均为Promise实例),而 new 操作之后必定为 Promise 对象,所以只需要保证执行完 then 方法后返回的仍然是 Promise 。如此我们就需要重新定义观察者(subscribe
) —— then
1 | // 区别于最初的变化值,我们需要将 observer 改为 成功 / 失败态监听函数 |
catch、finally
catch
方法返回一个Promise,并且处理拒绝的情况。它的行为与调用Promise.prototype.then(undefined, onRejected)
相同作为错误捕获
所以我们有了之前的 then 作为铺垫,这个方法就简单了许多
1 | catch = (onReject) => { |
finally
避免了同样的语句需要在 then()
和 catch()
中各写一次的情况。
finally
的返回结果仍为 Promise
finally
的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。执行完毕后其返回结果为
finally
所接收的最后一个value
1 | finally = (cb) => { |
Promise.resolve&reject
这里实现时应当将其写为静态方法,毕竟可以通过类直接调起。所以和我们上面实现的 resolve 方法还有区别,为了便于区分我们将原 resolve 方法转移至 constructor 中。
Promise.resolve 的主要作用在于将某个对象转化为 Promise 对象
故接收到的参数有以下几种可能的值:
Promise实例:不作处理,返回 Promise 实例
tenable 对象:转为 Promise 对象并立即执行该对象的 then 方法后返回
基本类型 / 非
tenable
对象 / 无参数:返回一个新的 resolved 态的 Promise 对象
1 | static resolve = (p) => { |
1 | static reject = (p) => { |
总结
Promise 归根结底就是观察者模式的视线版本,其发展离不开以下几点:
异步
- Push:new Promise,定义回调,then 方法完成 callback 收集,将依赖项 push 进对应 list 中【res push 进 resolveCallbackArr,rej push 进 rejectCallbackArr】。
- Run:回调传入 constructor 执行(若catch 到 err 则通过 reject 抛出)
- Wait:等待状态改变,调用 resolve / reject
- ForEach:resolve、reject更改状态,并执行 callbackList【resolveCallbackArr、rejectCallbackArr】 中的回调
- Eg:
1 | const observer = new Promise((res, rej) => { |
同步(手动调用resolve)
- Change:用户手动改变状态
- Run:回调(值)传入 constructor 执行
- forEach:直接调用 resolve,执行 callbackList【resolveCallbackArr】 中的回调
- Eg:
1 | const observer = new Promise((res, rej) => { |
代码地址
https://github.com/Burning-Shadow/promise-study