promise 笔记

151 阅读3分钟

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多条细节规范。