关于小程序网络数据请求延迟导致页面渲染失败问题
问题
微信小程序 wx.request(ajax)网络请求默认为异步,当 wx.request 发出请求后,程序流程将继续向下执行,此时 wxml 页面若有服务器返回的数据绑定,将输出一个空白。当异步请求完毕数据返回后,wxml 页面已经渲染完毕,返回的数据并不会显示在页面。
还有一种情况,在以 tabBar 结构的小程序里,当第一个页面正在执行 wx.request 数据请求,在外部数据返回页面前,用户快速切换到了其它 tabBar 页面,此时容易造成第一个页面数据返回中断。
解决方法1: 定义回调函数
回调函数的运行逻辑是这样的,有 A → B 两个按顺序运行的过程,A 开始执行异步数据请求,B 需要 A 返回的数据渲染页面,可以在 B 中定义一个 A.callback 回调函数。当过程 A 成功返回数据后判断 this.callback 是否被定义,如果回调被定义,就说明 B 正需要这个数据,别犹豫了赶紧执行吧;如果数据返回后 A 没发现 this.callback 被定义,说明数据返回的太快了,B 还没开始产生需要呢,于是 A 把返回的数据存入手机缓存或全局变量里,等 B 需要的时候自己取。而 B 发现全局变量后,就无需再定义 A.callback 函数,当然即使定义了 A 也不会执行,因为 A 已经运行结束。
示例代码
用小程序开发工具默认的代码,优化后运行如下:
app.js 全局脚本文件
//app.js
App({
onLaunch: function () {
// 获取用户头像和昵称信息,需要用户授权
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
wx.getUserInfo({
success: res => {
// 此处更新全局变量,但 index.onLoad 可能已经执行完毕
this.gbData.userInfo = res.userInfo;
// 由于 getUserInfo 是网络请求,可能会在 index.onLoad 完成之后返回
// 所以此处判断回调函数是否被 index.onLoad 定义,如果定义则执行之
if (this.userInfoCallback) {
this.userInfoCallback(res);
}
}
})
}
}
})
},
gbData: {
userInfo: null // 存储用户的头像路径和昵称信息的全局变量
}
})
index.js 页面脚本文件
//index.js
const app = getApp();
Page({
data: {
motto: '欢迎登录',
userInfo: {},
hasUserInfo: false //如果用户未授权,则不能获取用户信息
},
onLoad: function () {
// 由于 app.js 中的 getUserInfo 是异步网络请求, 全局变量 userInfo
// 可能没有被返回的数据填充,所以必须在 else 中给 app 定义一个回调函数
if (app.gbData.userInfo) {
this.setData({
userInfo: app.gbData.userInfo,
hasUserInfo: true
});
} else {
// 给 app 定义回调函数并在 app 中执行,接收 res 参数进行页面数据渲染
app.userInfoCallback = res => {
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
});
}
}
},
// 用户点击授权时响应函数,进行全局变量更新及页面数据渲染
f0(e) {
app.gbData.userInfo = e.detail.userInfo;
this.setData({
userInfo: app.gbData.userInfo,
hasUserInfo: true
});
}
})
index.wxml 页面渲染结构
<!--index.wxml-->
<view class="container">
<view class="userinfo">
<button wx:if="{{!hasUserInfo}}" open-type="getUserInfo" bindgetuserinfo="f0">获取头像昵称</button>
<block wx:else>
<image class="avatar" src="{{userInfo.avatarUrl}}"></image>
<text>{{userInfo.nickName}}</text>
</block>
</view>
<view>{{motto}}</view>
</view>
index.css 页面样式文件
/**index.wxss**/
.container, .userinfo {
display: flex;
flex-direction: column;
align-items: center;
}
.container {
height: 100vh;
justify-content: space-around;
}
.avatar {
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
index.json 文件保留空对象 {} 即可。
还有一种情况,如果同时存在 C 过程也需要返回数据,并且在 C 定义了一个 A.callback,此时在 A 中执行 this.callback 将会产生一个线程错误。因此这种回调方法可以用在程序结构简单的地方。
解决方法2:使用 promise 对象
promise 是 ES6 的新增功能,小程序内核支持,功能强大,详见阮一峰大神的教程: https://es6.ruanyifeng.com/#docs/promise