评论

用yield模改一个monadic coroutine

防止反复异步调用来造成callback hell,使用yield实现一个monadic coroutine,实现伪同步代码

相信大家都做过拿unionID的过程,大致需要先判断状态,然后wx.login进入session,之后session2unionID然后再是自己应用的webLogin
标记这几个状态是null session unionID login
所以我们会写

switch(state) {
    case null:
        wxlogin
        session2unionID
        webLogin
        getWebUser
        break
    case session
        session2unionID
        webLogin
        getWebUser
        break
    case unionID
        webLogin
        getWebUser
        break
    case login
        getWebUser
}

我们知道switch的break是可以省略的,于是这段代码应该是

switch(state) {
    case null:
        wxlogin
    case session
        session2unionID
    case unionID
        webLogin
    case login
        getWebUser
}

但这里有个问题,这些代码必须是同步的,否则只能callback hell,就要写很多代码,太麻烦了,而且要嵌套四五层。
目前所有的异步转同步库都是需要额外编译器才能实现的,而微信不支持,所以我索性利用生成器(function*yield)自己模改,这里和大家分享一下,希望有别的好想法能够改善这个流程。

小程序IDE支持了带有generator的typescript,利用这个可以用不到100行代码模改一个monadic coroutine(Continuation Monad)来实现回调转“伪同步”代码,不需要任何代码转换,甚至开了es6之后有generator了也可以直接用

具体原理参见我在知乎上写的一个详细说明,虽然是python的,不过我下面在注释里说明清楚了,就不反复赘述原理了

由于ts表达能力羸弱(缺少higher order type),所以干脆就用js写了,这个可以直接放在浏览器里执行,但是不能放到小程序IDE里,需要大家注意一下

// 这个类是一个promise,虽然我现在只给了一个callback,但是因为这个做法是CPS,还可以进一步写出来callCC(goto)来实现try-catch-throw,把异常callback封装进try-catch-throw里
class Pro {
    constructor(cont) {
       // cont是接收一个callback并把当前计算的值传递下去的函数
        this.cont = cont
    }
    run(callback) {
    	this.cont(callback)
    }
    static ret(value) {
        return new Pro(callback => {callback(value)})
    }
    bind(f) {
        return new Pro(callback => {
            this.run(a => {
                f(a).run(callback)
            })
        })
    }
}

// 我们需要一个函数接收monad bind传递下来的参数然后把剩下的部分generator(yield后等待接收的部分)变成一个新的可以用于继续生成monad的generator,相当于是一个partial apply
function* partialApply(generator, x) {
    while(true) {
       // 收到的东西依然是收到的
        let result = generator.next(x)
        if(result.done) {
            return result.value
        }
        // 产生的东西还是产生的
        // 并且送过来的东西还是送过来的东西,给原来的generator用
        x = yield result.value
    }
}

// 计算monad,这个很显然
function reduce(generator) {
    let result = generator.next()
    if(result.done) {return result.value}
    let mA = result.value
    return mA.bind(a => {return reduce(partialApply(generator, a))})
}

// 我们希望最后的monad还是变成一个函数,调用之后就是monad类型的结果,所以给了这个辅助函数
function monad(generatorFunc) {
    return function(...args) {
        return reduce(generatorFunc(...args))
    }
}

// 把异步封装成返回伪promise的函数,而不是直接调用callback
function wrap(f) {
    return function(...args) {
        return new Pro(callback => {f(...args, callback)})
    }
}

// 因为这个只是一种流程控制,所以我们这里并不需要真的找一个异步函数,自己伪造一个实现即可
function callAsync(a, f) {return f(a)}
let call = wrap(callAsync)

// 最后这里是伪同步的代码
monad(function*(){
    let val1 = yield call(1)
    let val2 = yield call(2)
    return Pro.ret(val1+val2)
})().run(console.log)
最后一次编辑于  03-09  
点赞 1
收藏
评论