主题:

实现异步编程的两种方法:

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可能在请求发出前失效,也有可能是服务端返回的响应中失效。

所以我们需要做两处的请求拦截:
请求拦截;
响应拦截;

  1. //以fly.js封装的request为例
  2. //session是我们的维护的登陆逻辑,包含了前端对checkSession,login,以及refreshLogin3个部分。
  3. fly.interceptors.request.use(async(request)=>{
  4. const jsCode=await session.checkSession()
  5. return request
  6. })
  7. fly.interceptors.response.use(async(response)=>{
  8. if(response.status == 401){
  9. //服务端返回登陆态失效
  10. const jsCode=await session.refreshLogin()
  11. return fly.request(response.request)
  12. }
  13. })

需要注意的是,通常会有并发的多条请求同时返回登陆态失效,或者checkSession失效,这时需要其中一条请求刷新code,其他请求推入队列,使用Promise挂起,等到code刷新成功后,并发的其他请求消费刷新后的code。

使用Promise挂起请求队列的方法是,在refreshLogin中return一个pendding状态的Promise,后续的.then模块不会再执行,直到请求队列中的Promise执行resolve,请求才会继续执行。【重点】

需要思考的是,并发的请求中也有可能出现checkSession同时失效的时候,这个时候也是使用同样的方法,避免多次请求code吗

  1. class Session{
  2. refreshLogin=false;
  3. refreshLoginQueue=[];
  4. checkSession(){
  5. return new Promise(function(resolve){
  6. //检查本地是否缓存code
  7. let jsCode=getStorageSync('code');
  8. if(jsCode){
  9. wx.checkSession(function(){
  10. //有效
  11. resolve(jsCode)
  12. },async function(){
  13. //无效,执行login
  14. //jsCode=await this.login()
  15. //resolve(jsCode)
  16. //TODO这里执行refreshLogin呢
  17. jsCode= await this.refreshLogin()
  18. resolve(jsCode)
  19. })
  20. }
  21. })
  22. }
  23. login(){
  24. return new Promise(function(resolve){
  25. let jsCode=getStorageSync('code');
  26. if(jsCode){
  27. resolve(jsCode)
  28. }
  29. else{
  30. wx.login(function(res){
  31. resolve(res.code)
  32. })
  33. }
  34. })
  35. }
  36. refreshLogin(){
  37. return Promise.resolve()
  38. .then(()=>{
  39. if(this.refreshLogin){
  40. return new Promise(function(resolve,reject){
  41. this.refreshLoginQueue.push([resolve,reject])
  42. })
  43. }
  44. this.refreshLogin=true
  45. })
  46. .then(async()=>{
  47. wx.showLoading({icon:'none',title:'刷新登陆中...'})
  48. const code=await this.login()
  49. wx.hideLoading()
  50. this.refreshLoginQueue.forEach(([resolve,reject])=>{
  51. resolve()
  52. })
  53. this.refreshLogingQueue=[];
  54. this.refreshLogin=false;
  55. return code
  56. })
  57. }
  58. }