这个问题,挺新颖的。是在一次开发小程序中遇到的,就是在倒计时的时候会在倒计时在某个时间时给用户展示一个图片,用户可点击这个图片放大查看,然后就在点击查看图片(要经过网络请求获得)的时候,setInterval设置的倒计时被中断,即被清掉了。这显然不是我要得效果,我要得效果是即使用户点击图片放大查看也不影响倒计时的运行。
WXML部分:
<view class='countDown'>倒计时:<text class='countDownNum'>{{countDownNum}}</text>s</view>
<view class='relativeImageView'><image class='relativeImage' src='{{imageUrl}}' bindtap='showBigImage'></image></view>
原JS部分:
data: {
timer: ''
},
countDown: function (){
let that = this;
let showTipTime = 30;
let countDownNum = 60;
that.setData({
timer: setInterval(function () {
countDownNum--;
that.setData({
countDownNum: countDownNum
})
if (countDownNum == showTipTime) {
that.setData({
imageInfoHidden: false
})
} else if (countDownNum == 0) {
clearInterval(that.data.timer);
}
}, 1000)
})
},
//图片点击,预览大图效果
showBigImage:function(){
let that = this;
let imageUrl = wx.getStorageSync("imageUrl");//这个imageUrl是之前发送请求保存下来的图片路径
wx.previewImage({
current: imageUrl,
urls: [imageUrl],
complete: function(){
},
})
},
大家可以将代码复制,然后去查看效果,发现点击查看图片后,然后点击图片,图片会消失,但是倒计时也停止,即被清掉了。
那么怎么分析这个问题并解决呢?
我给出的分析是这样的(是我自己分析的,肯定会有不足之处,希望大神看了勿喷,还请指正,感激不尽):
因为setInterval和setTimeout均属异步线程(严格上应该要被称为伪异步编程,毕竟还是那句老话,js是单线程的),HTTPS请求默认也是异步执行的,然而js执行代码时,是单线程的,在整个页面渲染完成之后,js需要执行的基本上都是用户的操作,即与用户的交互,其中包括资源请求等。
由于setInterval产生的倒计时效果属于异步线程,按道理来说,只要界面内有用户的操作,那么便优先执行用户的操作,然后再执行异步线程里的内容。这里我这么解释吧,js执行代码是单线程的,异步线程里的任务是在同步线程执行完之后再执行的,也就是说,执行的顺序是同步>异步,js执行代码相当于把异步任务放在了同步任务的最后面,异步线程里的任务跟同步线程里的代码一样,是要按照从上至下,从左至右的顺序执行。
好,那么当用户点击预览图片这个事件(同步),那么先执行这个事件内的具体任务,我们发现showBigImage函数里有一个请求图片的异步操作,这个时候,由于先进入到了showBigImage事件,随后要异步请求图片,js又是单线程的,所以它只好先把setInterval关掉,停止对它的操作,优先去执行获取图片的异步操作,然而获取图片之后,本应该要返回再执行setInterval的,但由于js将先清掉了(相当于clearInterval),所以倒计时会停止。
解决:
很明显,我们要在请求imageUrl之后再次启动定时器,但是这样话有延迟,意思是请求到imageUrl需要点时间(虽然这个时间在网络环境很好时很短),倒计时会不真实,所以倒计时函数暂时不能写进complete回调里,那么到底怎么解决呢?
将倒计时函数写在请求imageUrl之前?博主我试过,当时是可以的,为何这样说?是因为当时测试的时候只测试了两遍,两遍都成功了,便以为可以,但是后台我又测试了几遍,尤其是点击预览图片的时候,这个时候,定时器都不会被关闭。从而出现负数的情况,很是尴尬啊。
所以在每次点击图片预览大图时,如果将倒计时写在请求之前,都会重新启动异步线程setInterval,由于要隔1s才会进入到倒计时函数countDown,那么在这1s内,js会去执行请求imageUrl,即在请求imageUrl之后,setInterval(也就是倒计时效果会被停止)会被中断。所以为了防止这种情况,理应该要把setInterval异步线程的执行提到获取imageUrl的前面(这里也体现到了js是单线程解析代码的)。
但是不行,哎,我个人觉得是wx.previewImage接口的坑,而且很奇葩的是,我设计的倒计时到指定时间后会跳转到另外一个页面,但是如果一直将界面停留在预览图片界面(如上图左边),即使页面跳转了,这个窗口依旧不会消失!!!博主现在暂时想不出有什么方法可以解决,希望有高手指点...
最后为了妥协这个坑(我实在是跳不出来啊......),下面是升级后的代码:
data: {
timer: ''
},
countDown: function (){
let that = this;
let showTipTime = 30;
let countDownNum = 60;
that.setData({
timer: setInterval(function () {
countDownNum--;
that.setData({
countDownNum: countDownNum
})
if (countDownNum == showTipTime) {
that.setData({
imageInfoHidden: false
})
} else if (countDownNum == 0) {
clearInterval(that.data.timer);
}
}, 1000)
})
},
//图片点击,预览大图效果
showBigImage:function(){
let that = this;
let imageUrl = wx.getStorageSync("imageUrl");//这个imageUrl是之前发送请求保存下来的图片路径
wx.previewImage({
current: imageUrl,
urls: [imageUrl],
complete: function(){
that.countDown();//其实就在这里增加了一个调用倒计时的函数
},
})
},