- 关于我在微信小程序学习初期,踩的最大坑,同步和异步(大佬教我写登录系列)
最近在学小程序,今天在做登录校验。我写了个一键登录,然后通过云函数1获取openid,用过云函数2校验数据库看是否有这个openid的用户(用的.where().count()方法),有的话返回值res.result.total会是1,返回0就验证不通过。奔着这个逻辑出现了以下尴尬的情况: const app = getApp() var isLogin1=0; Page({ data: { userInfo: null, isLogin:0 }, onLoad(options) { }, chkuid() { let id = app.globalData.user_openid //登录核对用户ID wx.cloud.callFunction({ name: 'getWxIdChkFun', data: { id: id } }) .then(res => { console.log("校对完毕!", res) console.log("chkuid里的",res.result.total) isLogin1= res.result.total console.log("chkuid里的",isLogin1) this.setData({ isLogin: res.result.total }) console.log("chkuid里的",this.data.isLogin) }) .catch(res => { console.log("校对失败!", res) }) }, login() { let isUser = 0 this.chkuid() isUser = isLogin1 console.log('login里的isUser1',isUser) isUser=this.data.isLogin console.log('login里的isUser2',isUser) }, }) [图片] 问题非常尴尬,login执行完了chk的结果才出来。经过“茜茜又困了🐽”大佬解惑,我发现login里的代码块其实是同步执行的,也就是按顺序一条一条下来,但是执行到第二条chkuid进入函数内部是,发现这里面的东西是异步的,于是系统开了个支线继续跑chkuid这个函数,因为那货是异步所以login接着往下执行,但是chkuid的运算耗时比那两个Log长,所以出现了先把两个log输出,再输出chkuid结果的现象。蛋疼的地方在于我得用到chkuid里的total,所以这种操作让我没法做isLogin==1的判断。 我百度了下,发现解决这种情况的方法,好像是用Promise或async/await,于是舔着脸又去抱大腿,这回大佬上了硬菜,码图并茂的给我科普了下两种方式的写法: 一、Promise写法: chkuid() { let id = app.globalData.user_openid return new Promise((resolve,reject)=>{ wx.cloud.callFunction({ name:"getWxIdChkFun", data:{ id:id } }) .then(res=>{ console.log("校验成功",res) resolve(res.result.total) }) .catch(err=>{ console.log("校验失败",err) reject(err) }) }) }, login() { let isUser=0 this.chkuid().then(res=>{ isUser=res console.log("then里的isUser",isUser) }) console.log("then外的isUser",isUser) [图片] 写到这里,又比较尴尬,为啥这个isUser在then之外还是0,我想得到的是一个在login里随便都能用的变量。。。。(原因找到了,详见下文) 大佬教的方法二:async和await chkuid() { let id = app.globalData.user_openid return new Promise(resolve => { wx.cloud.callFunction({ name: "getWxIdChkFun", data: { id: id } }) .then(res => { isLogin1 = res.result.total this.setData({ isLogin: res.result.total }) resolve() }) .catch(err => { console.log("校验失败", err) reject(err) }) }) }, async login() { let isUser = 0 await this.chkuid() isUser = isLogin1 console.log('login里的isUser1', isUser) isUser = this.data.isLogin console.log('login里的isUser2', isUser) } [图片] 此时得到了我理想的效果,在chkuid中把res的结果抛给一个全局变量(通过Page外部的var temp或是this.setData()给到Page.data里都行),然后在其他函数中就能随意使用这个变量。 ———————————————————————————————————————————————————————————————————— 经过详细查阅大佬给的两串代码,发现其实chkuid的写法是一样的,无非一个是在里面给全局变量赋值,然后resolve()返回空,另一个是不给全局变量赋值,resolve(res)返回res的Promise。 但是底下调用时,对应的有两种方法 (一)如果chkuid里的then赋值给全局变量了,login里直接用就完事了。 (二)如果chkuid里的then没赋值给全局变量,而是resolve(res)了,那么login中要获得这个值就得用这种写法 let isUser=0 this.chkuid().then(res=>{ isUser=res }) 现在就剩一个问题了,为啥这个isUser明明成了1,出了那个then又变0了? ———————————————————————————————————————————————————————————————————— 2022/7/13更新: 上述问题经过“茜茜又困了🐽”大佬帮我解惑,原来不是没赋值成功,还是异步的问题,在外面那个log加个SetTimeOut稍微延迟下主进程(让进程飞一会儿~),就也能输出1了。 我本以为事情到此结束,结果,更大的坑来了。我这个登录页面的本意是: 通过app.js里调用云函数获取openid,然后在login.js里写了两个函数,一个是login,一个是chkuid。chkuid调用另一个云函数,那个云函数的作用是把openid的值跟云数据库User表匹配,用的.count方法,如果这个openid在User表里有,那返回的res.result.total会为1,为0则表示没有这个用户。 [图片] 问题来了,不管我怎么按,右边模拟器都不会弹框提示我得授权,err输出fail can only be invoked by user TAP gesture。问题是你授权界面弹都没弹出来啊。这个问题崩溃了一天。[图片] 后来CSDN上找到另一个大佬的帖子,https://blog.csdn.net/qq_45233592/article/details/121910930,大概意思是getUserProfile跟async/await八字不合。 [图片] 人在迷茫的时候就是死马当活马医,也不管是不是一样了(没仔细看uni.,不是wx. ) 跟“茜茜又困了🐽”大佬交流了一番,大佬斩钉截铁的说肯定还是异步问题,跟我上面那玩意儿没关系。让我尝试Promise写法试试。于是有了以下代码 chkuid() { let id = app.globalData.user_openid return new Promise(resolve => { wx.cloud.callFunction({ name: "getWxIdChkFun", data: { id: id } }) .then(res => { this.setData({ isLogin: res.result.total }) //resolve() resolve(res.result.total) }) .catch(err => { console.log("校验失败", err) reject(err) }) }) }, login() { let isUser=0 this.chkuid().then(res=>{ isUser=res console.log(isUser) wx.getUserProfile({ desc: '获取用户信息', success: (res) => { console.log(res.userInfo) if (isUser == 1) { //设置全局用户信息 app.globalData.userInfo = res.userInfo //设置局部用户信息 this.setData({ userInfo: res.userInfo }) wx.switchTab({ url: '/pages/today/today' }) } else { wx.showToast({ icon: 'error', title: '未授权用户', }) } }, fail: (err => { console.log(err) }) }) }) }, [图片] 依然得到那个结果。。。。。 直到刚才,大佬私信我,为啥不试试,把chkuid放在success里呢,于是我修改代码 login() { wx.getUserProfile({ desc: '获取用户信息', success: async (res) => { let isUser = 0 await this.chkuid() isUser = this.data.isLogin console.log(isUser) //确认能拿到total值后,开始弹窗 console.log(res.userInfo) if (isUser == 1) { //设置全局用户信息 app.globalData.userInfo = res.userInfo //设置局部用户信息 this.setData({ userInfo: res.userInfo }) wx.switchTab({ url: '/pages/today/today' }) } else { wx.showToast({ icon: 'error', title: '未授权用户', }) } }, fail: (err => { console.log(err) }) }) }, 把chkuid写在Success里,然后在找之前CSDN上的那个人的写法,在success: async (res)=>{} 和 await this.chkuid 加上标识。 登录功能成功生效。
2022-07-13 - 这个库能轻松解决99%的异步和逻辑加载时机问题(异步篇)
[图片] 你是否纠结过底层业务逻辑(登陆、获取用户信息等)到底是放app.js的onLaunch还是page的onLoad里比较好,或者因为异步问题被迫放在了onload,我们来分析一下优劣 - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - 分析 onLaunch处理 优点:底层业务逻辑集中并且只需写一次,比较好维护 缺点:目前没有一个理想的方案来解决onLaunch和onLoad的异步问题,包括注册回调、重写onLoad、请求拦截等。 onLoad处理 优点:因为不涉及跨页面通知,因此异步逻辑比较好处理 缺点:每个页面都得写一次底层业务逻辑,非常繁琐,而且既然是公用的底层业务逻辑,分散在每个页面的onLoad里,好像也不大对劲。 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - 抉择 按照高内聚低耦合的原则,那逻辑和数据放onLaunch里肯定的,不应该和普通page逻辑耦合在一起,通用的数据和逻辑应该在入口去处理,执行一次到处使用,就像vue的main.js一样,会注册一些技术层的基础设施(路由、状态管理等插件),那业务层的基础设施不就是token、用户信息、所在位置等逻辑吗? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - 想象中的最佳实践 那我们的目标就是如何满足两者的优点,避免两者的缺点,做到真正的“高内聚低耦合” 1.保持底层业务逻辑写在入口app.js,避免耦合page里的逻辑 2.能在任何page里第一时间拿到globalData数据 3.使用方便,做到在业务开发中无感知,不需要写额外的调用、通知等代码 4.无任何副作用,不会影响其他功能,比如重写阻塞onLoad 5.灵活可配,适用以后此类任何业务 - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 梦想成真先看一段代码 ⬇️ // page.js export default { name: 'Home', onLoadLogin(){ //登录成功(拿到token) && 页面初始化完成 //Tips:适用于某页面发送的请求依赖token的场景 }, onLoadUser(){ //页面初始化完成 && 获取用户信息完成 //Tips:适用于页面初始化时需要用到用户信息去做判断再走页面逻辑的场景 }, onReadyUser(){ //dom渲染完成 && 获取用户信息完成 //Tips:适用于首次进入页面需要在canvas上渲染头像的类似场景 }, onReadyShow(){ //小程序内页面渲染完成 && 页面显示 //Tips:适用于需要获取小程序组件或者dom,并且每次页面显示都会执行的场景 }, } 应该懂什么意思了吧?是不是你理想中的样子,使用起来跟没有似的 ⬆️ 这段示例代码满足了上面的第2、3、4条目标 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 再来看一段 ⬇️ // app.js // 配置自定义钩子,所有钩子都可以随意组合搭配使用,执行机制类似于Promise.all(但不是用Promise实现的) CustomHook.install({ 'Login':{ // 自定义钩子名称、必须大写字母开头 name:'Login', watchKey: 'token', onUpdate(token){ //有token则触发此钩子 return !!token; } }, 'User':{ // 自定义钩子名称 name:'User', watchKey: 'userInfo', onUpdate(user){ //获取到userinfo里的userId则触发此钩子 return !!user.userId; } } //依赖globalData中数据 }, globalData) 怎么样,是不是很棒,依赖globalData,名字可配,连触发规则都可配,而且还附加了可随意组合的功能(意外还解决了页面内逻辑执行时机问题,在下篇讲) ⬆️ 这段示例代码满足了上面的第1、5条目标。 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -我是分割线 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 是不是跃跃欲试了,那就赶紧试试,好用回来告诉我! ⬇️(公司内部已接入两年了很稳定) GitHub:https://github.com/1977474741/spa-custom-hooks [图片]
2023-07-07