主题:
实现异步编程的两种方法:
callbacks(回调函数)
并不是所有回调函数都是异步执行的,例如foreach的异步函数就是同步执行的。
Promise
·可以实现链式调用,每一个then块会返回另一个promise,而且上一个then块内的回调函数返回的结果,可以被下一个then所使用
登陆逻辑
以mdnf小程序的登陆为例:
小程序需要获取code传给服务端,服务端通过微信的code2session获得用户session确认用户身份。
在这个登陆流程中,前后端共同使用小程序的code(小程序自己维护的登陆态),服务端没有独立维护新的登陆态。
小程序的code有以下几个特点:
1、完全由微信维护,用户在线时间越长,登陆态越不容易过期
2、服务端使用code2session接口校验过code后,code会失效,如果服务端再次拿同样的code请求code2session,微信接口会报错,返回errcode为40029(code失效),40163(code已经被使用过)
3、每个code的有效期是5分钟,如果获取code的5分钟内,没有进行code2session,code会失效
4、小程序前端提供了一个api用来校验code有效期,checkSession。但是这个api的校验并不是100%准确的,会出现前端使用checkSession校验code有效,服务端使用code请求微信接口返回无效。
通过这几个特点可以知道,code可能在请求发出前失效,也有可能是服务端返回的响应中失效。
所以我们需要做两处的请求拦截:
请求拦截;
响应拦截;
//以fly.js封装的request为例
//session是我们的维护的登陆逻辑,包含了前端对checkSession,login,以及refreshLogin3个部分。
fly.interceptors.request.use(async(request)=>{
const jsCode=await session.checkSession()
return request
})
fly.interceptors.response.use(async(response)=>{
if(response.status == 401){
//服务端返回登陆态失效
const jsCode=await session.refreshLogin()
return fly.request(response.request)
}
})
需要注意的是,通常会有并发的多条请求同时返回登陆态失效,或者checkSession失效,这时需要其中一条请求刷新code,其他请求推入队列,使用Promise挂起,等到code刷新成功后,并发的其他请求消费刷新后的code。
使用Promise挂起请求队列的方法是,在refreshLogin中return一个pendding状态的Promise,后续的.then模块不会再执行,直到请求队列中的Promise执行resolve,请求才会继续执行。【重点】
需要思考的是,并发的请求中也有可能出现checkSession同时失效的时候,这个时候也是使用同样的方法,避免多次请求code吗
class Session{
refreshLogin=false;
refreshLoginQueue=[];
checkSession(){
return new Promise(function(resolve){
//检查本地是否缓存code
let jsCode=getStorageSync('code');
if(jsCode){
wx.checkSession(function(){
//有效
resolve(jsCode)
},async function(){
//无效,执行login
//jsCode=await this.login()
//resolve(jsCode)
//TODO这里执行refreshLogin呢
jsCode= await this.refreshLogin()
resolve(jsCode)
})
}
})
}
login(){
return new Promise(function(resolve){
let jsCode=getStorageSync('code');
if(jsCode){
resolve(jsCode)
}
else{
wx.login(function(res){
resolve(res.code)
})
}
})
}
refreshLogin(){
return Promise.resolve()
.then(()=>{
if(this.refreshLogin){
return new Promise(function(resolve,reject){
this.refreshLoginQueue.push([resolve,reject])
})
}
this.refreshLogin=true
})
.then(async()=>{
wx.showLoading({icon:'none',title:'刷新登陆中...'})
const code=await this.login()
wx.hideLoading()
this.refreshLoginQueue.forEach(([resolve,reject])=>{
resolve()
})
this.refreshLogingQueue=[];
this.refreshLogin=false;
return code
})
}
}