Promise 是为了解决过去callback 嵌套地狱问题的一种编码规范。跟具体使用什么语言实现没关系。开始的时候只是一种社区方案,但github 上不缺大牛,硬生生把零散的方案整合起来,并提交提案到了ecma组织,并最终形成了浏览器规范,实现大一统。
最近回看了一下promise 的发展历史,其每一条规则都有着自己的意义,例如为了语义,为了吸纳兼容,为了分布式状态管理。。。
我现在试试重走promise 的设计之路
若干年前的异步回调
func1(1, ()={
func2(2, ()={
func3(3, ()={
})
})
})
现在我想设计一个promise,并参考jquery 的链式调用有
class Promise(){
value=null
constuctor(fn){
fn(resolve)
}
resolve(x){
this.value = x
}
then(cb){
return new Promise((resolve)=>{
cb()
resolve()
})
}
}
var aP = new Promise((resolve)=>{
setTimeout(()=>{
resolve(1)
}, 1000)
})
aP.then(()=>{})
但promise 是异步操作哦,例如网络请求还没有返回数据怎么办?先保存,等有数据再执行
class Promise(){
status: 'pending' // 标记一下是不是有异步数据了
cbList=[]
value=null
constuctor(fn){
fn(this.resolve)
}
resolve(x){
value=x // 保存值
this.status='ok'
cbList.forEach(({cb,resolve})=>{
cb(x)
resolve()
})
}
then(cb){
return new Promise((resolve)=>{
if(this.status === 'ok'){
cb(this.value) // 同步第二次调用
}else{
this.cbList.push({
cb,
resolve
})
}
})
}
}
我就只想到这里了,但看看规范,还缺很多,例如onfulfilled 和onrejected 为什么不允许同步回调呢?
when.js 的作者就在promise 的讨论里面质疑为什么一定要在 execution context stack contains only platform code 的时候才执行,因为在早期的when.js 用户群里面就允许同步和异步,也没什么人投诉过。随后promise 群其他人抛出了Mark S. Miller的回答,大概是
- (首先promise 是设计成可以在其他语言里面实现的,例如lua)同时允许同步和异步,将会失去两者的优点,但同时带有了两者的缺点。For a quick summary of the issue, see Section 3 and Table 1 at here。
- 实际上,开发者的日常工作就是在代码里面填坑,加功能而已,并没有精力去了解别人的promise 内部,分析onfulfilled 和onrejected是会产生同步副作用还是异步副作用,自己在promise 后面写的代码是会在onfulfilled之前执行还是onfulfilled 之后执行。在设计一门语言的时候,作者都是希望程序不出bug,或者有bug 就提早警告的。所以在设计promise 的时候建议不要搞这样加重智力负担的方案。
为什么[[Resolve]](promise, x) 这么复杂?
- 为了吸纳一些同类方案(when.js, bluebird),减少其他人迁移的成本,所以尽量兼容了thenable 的概念
再对比一下promise A+ 规范,发现作者们考虑的事情还有更加多
- 为了更语义化,减少额外的try catch,让出现异常都能链式捕捉到,所以区分了fulfilled 链,和rejected 链。开发人员可以按需统一处理reject,或者就近处理reject
- 为了保证多次调用都是同一个fulfilled 的值,规定了状态变化只能从pending 到fulfilled或者rejected,而且只能变一次
结语
- promise 说简单,可以很简单,不到10条细节规范就差不多了。但能有今天大一统的地位,靠得是里面另外30多条细节规范。