问题场景
最近在写小程序项目的过程中,真的是每走一步都是个“巨坑”。这次在实现免登录的过程中,我们需要在app.js里先拿到返回的token
之后,在首页携带token
发送请求获得首页需要展示的数据。
OK,这是我们想要小程序执行的顺序,但是小程序的执行顺序是先执行app.js onLaunch,通过dd.getAuthCode
获取免登授权码,然后通过 access-token
和 code
获取 userid
,根据 userid
获取 token
。这整个过程都是异步的,那么我们在page onload里面需要携带token
请求数据时,onLaunch里的异步请求还没有拿到token
,但是此时page onload在还未拿到数据的情况下就已经展示出来了。
给大家看一下控制台的输出顺序:
可以看出来先执行了home页的数据输出,之后app里请求到了数据也跟home页无关了,因为home页已经执行完了。
解决过程
有的文章上说在app.js上自定义一个函数(下行代码中的tokenCallBack方法),当home.js没有拿到token
的时候,就调用app里的这个方法获取token
之后再执行下面的代码。
home.js代码:
const app = getApp();
Page({
data:{
// 存储首页所有的展示信息
allList:[],
},
async onLoad() {
// 从本地获取 token
let token = dd.getStorageSync({ key: 'token' }).data;
// 判断本地是否有 token,如果没有,就在本地存一个 token,如果有 token,就直接调用接口
if (token == null) {
// 在顶级对象里面添加一个 tokenCallBack 方法
app.tokenCallBack = async (token) => {
if (token) {
dd.setStorageSync({
key: 'token',
data: token
})
// 发起请求,请求接口
let result = await app.httpsReq('GET', '/**/**')
if(result.status === 200){
this.setData({
allList : result.data.data
})
}
}
}
} else {
// 如果有 token ,就直接发起请求
let result = await app.httpsReq('GET', ''/**/**'')
}
},
});
app.js代码:
APP({
onLaunch(options) {
// 调用 istoken方法,实现免登陆
this.isToken();
},
isToken(){
// 将顶级对象的 this 存储到 this2 中
const this2 = this
// 获取免登授权码(方法 API 文档提供)
dd.getAuthCode({
success:function(res){
// 存储 authorCode
this.authCode = res.authCode
// 将方法 getAuthCode 的 this 存储到 _this 中
let _this = this
// 根据 appkey 和 appsecret 获取 access-token
dd.httpRequest({
url:'https://oapi.dingtalk.com/gettoken?appkey=**&appsecret=**',
method:'GET',
success: (res) => {
_this.access_token = res.data.access_token
// 根据 access-token 和 code 获取 userid
dd.httpRequest({
url:'https://oapi.dingtalk.com/topapi/v2/user/getuserinfo?access_token=' + _this.access_token + '&code=' + _this.authCode,
method:'POST',
success: async(res) =>{
_this.userid = res.data.result.userid
// 根据 userid 获取 token
let result = await this2.httpsReq('POST','/**/**',{'userid':_this.userid})
// tokenCallBack 方法用于在本地存储 token
if(this2.tokenCallBack){
this2.tokenCallBack(result.data.data.token)
}
}
})
}
})
},
fail:function(err){
console.log(err);
}
})
},
})
很遗憾,还是没能解决这个问题。期间也测试了很多种写法,但是如果不解决这个异步的问题,结果都是一样的。
最终代码
最后我搭档无意间点进了一篇文章,上面的写法都是一样的,只不过是把home.js 里写在onLoad中的代码提到了一个方法里(下行中的getdata()
),然后在onLoad里调用这个方法,就解决这个问题了。。。说实话,很草率,但确实有用…
home.js代码:
const app = getApp();
Page({
data: {
// 存储首页所有的展示信息
allList: [],
},
onLoad() {
// 获取首页数据
this.getdata()
},
// 用来发起请求获取首页数据的方法
async getdata() {
// 从本地获取 token
let token = dd.getStorageSync({ key: 'token' }).data;
// 判断本地是否有 token,如果没有,就在本地存一个 token,如果有 token,就直接调用接口
if (token == null) {
// 在顶级对象里面添加一个 tokenCallBack 方法
app.tokenCallBack = async (token) => {
if (token) {
// 将 token 存到本地
dd.setStorageSync({
key: 'token',
data: token
})
// 发起请求,请求接口
let result = await app.httpsReq('GET', '/**/**')
if (result.status === 200) {
// 通过 setData 修改数据
this.setData({
allList: result.data.data
})
}
}
}
}
else {
// 如果有 token ,就直接发起请求
let result = await app.httpsReq('GET', '/**/**')
if (result.status === 200) {
// 通过 setData 修改数据
this.setData({
allList: result.data.data
})
}
}
},
});
app.js代码:
APP({
onLaunch(options) {
// 调用 istoken方法,实现免登陆
this.isToken();
},
isToken(){
// 将顶级对象的 this 存储到 this2 中
const this2 = this
// 获取免登授权码(方法 API 文档提供)
dd.getAuthCode({
success:function(res){
// 存储 authorCode
this.authCode = res.authCode
// 将方法 getAuthCode 的 this 存储到 _this 中
let _this = this
// 根据 appkey 和 appsecret 获取 access-token
dd.httpRequest({
url:'https://oapi.dingtalk.com/gettoken?appkey=**&appsecret=**',
method:'GET',
success: (res) => {
_this.access_token = res.data.access_token
// 根据 access-token 和 code 获取 userid
dd.httpRequest({
url:'https://oapi.dingtalk.com/topapi/v2/user/getuserinfo?access_token=' + _this.access_token + '&code=' + _this.authCode,
method:'POST',
success: async(res) =>{
_this.userid = res.data.result.userid
// 根据 userid 获取 token
let result = await this2.httpsReq('POST','/**/**',{'userid':_this.userid})
// tokenCallBack 方法用于在本地存储 token
if(this2.tokenCallBack){
this2.tokenCallBack(result.data.data.token)
}
}
})
}
})
},
fail:function(err){
console.log(err);
}
})
},
})
我跟我搭子想了很多种猜测,来解释为什么把这些代码提到一个方法里,就可以解决这个问题,但是都觉得没有确切的知识支持。
我觉得比较合理的解释可能就是,小程序首次启动或页面首次加载时,会先执行初始化,初始化
data
中的数据和自定义的一些方法完成之后,会触发onLaunch
或onLoad
回调函数,此时数据和方法都已经渲染好了,直接拿来用就ok了。但是官方文档并没有很详细地说明小程序页面的生命周期过程中分别都做了哪些准备工作,所以上面我们的猜测可能是错误的,但是我们实在想不到更好的解释了。
如果大家有其他或更好的解决方法,欢迎不吝赐教!
如有误,请指正!