相信大家都做过拿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)