提示:
本文为js栏目:前端项目开发踩坑01————利用setTimeout模拟出类似setInterval 及宏任务微任务
————webpack常用插件的安装
前言
大屏项目等利用定时器固定时间刷新数据。
setInterval包裹Promise导致,Promise的回调执行时间与预期timeout不符的解决方案:利用setTimeout模拟出类似setInterval。
提示:以下是本篇文章正文内容,下面成功案例可供参考
先放解决方案。
解决方案
先放解决方案。
setTimeout(function() {
new Promise(function(resolve) {
resolve();
}).then(function() {
setTimeout(function() { },timeout)
})
},timeout)
Vue项目:
export default {
data() {
return {
timeout: null,
canceled: false,
callback: null,
delay: 10000,
}
},
mounted() {
const that = this
that.run()
},
created() {},
beforeDestroy() {
clearInterval(this.timeout)
},
methods: {
run() {
const that = this
if (this.canceled) {
return
}
that.getDataCount()
},
getDataCount() {
const that = this
getDatePromise().then(res => {
if (res.code == '200') {
clearInterval(that.timeout)
that.timeout = null
that.timeout = setTimeout(that.run, that.delay)
} else {
that.run()
return
}
}).catch(() => {
that.run()
}).finally(() => {
})
},
start() {
this.canceled = false
this.run()
},
cancel() {
clearTimeout(this.timeout)
this.timeout = null
this.canceled = true
}
}
}
疑难杂症
最近干了一个大屏项目,一般来说,大屏数据的更新要么用计时器,要么用长连接,websocket,signal这些对吧。
然后根据领导的要求,数据的更新用的计时器。活爹,用就用呗。
然后就遇到问题了,一开始的想法是使用setInterval()
中固定时间调用接口,后来发现不行。
setTimeout、setInterval和Promise
大多数项目中,我一般封装request.js时,会选择把响应结果封装成Promise 。在这个项目中一开始没有考虑到setInterval和Promise 执行机制的问题。
JavaScript中定时器主要有setTimeout和setInterval,但是它们在执行时往往和我们设置的延迟时间有出入,这点大家都懂。简单解释一下。
首先记住:
JavaScript始终是同步的并且是单线程的。
JavaScript 在浏览器环境中主要是单线程的,在某些情况下(如 Node.js 或 Web Workers)可以表现出多线程的特性。然而,JavaScript 的设计哲学和主要执行模型是基于单线程的。
JavaScript 通过异步编程模型来处理耗时的操作,如网络请求、定时器、DOM 事件等。这些操作被划分为宏任务。
这涉及到微任务和宏任务,
微任务和宏任务
首先代码可以划分为同步代码和异步代码,异步代码又被划分为宏任务和微任务,微任务与宏任务分属不同的任务队列(Task Queue)。
先执行同步代码,后执行异步代码,执行异步代码时,先执行微任务队列中的微任务。而宏任务不会阻塞主线程的执行,而是将它们放入任务队列中等待。当主线程空闲时,事件循环会从任务队列中取出任务并执行它们。
同步->微任务->宏任务,循环往复。
微任务和宏任务最大的不同在于,如果在执行微任务的过程中我们往任务队列中新增了任务,浏览器会全部消费掉为止,再进入下一个循环。
这使得 JavaScript 能够在不阻塞主线程的情况下处理异步操作。
setTimeout和setInterval都属于宏任务(MacroTask/Task),Promise 属于微任务(MicroTask),且Promise在实例化的过程中所执行的代码都是同步进行的。
如果遇到这种情况,宏任务内包裹微任务
setInterval(()=>{
//code1..
new Promise(resolve =>{
resolve();
}).then(()=>{
// code2..
});
},timeout);
遇到setInterval,异步宏任务,将code1..
放入宏任务队列中;
new Promise在实例化的过程中所执行的代码都是同步进行的,但是.then中注册的回调是异步执行的,所以将其放入微任务队列中。
而计时器在timeout时间后生成的又是宏任务,只是将事件放消息队列,真正执行的时间并不确定,有可能上一个计时器任务没执行完又进来一个计时器任务,所以会造成.then内执行时间与预期执行时间不同,这就造成了奇奇怪怪的bug。
事件循环Event Loop
都讲到这了,拓展一下什么叫事件循环。
JavaScript的执行机制是居于事件循环的。
再次重申,**JavaScript始终是同步的并且是单线程的。**同一时间,浏览器只能执行一个任务。
为了避免某些长时间任务造成无意义等待,JavaScript 引入了异步任务队列,
如图:
同步任务放在执行栈中执行,异步任务由Web Apis处理放到任务队列中,执行栈中的任务执行完毕会直接去查询任务队列中是否有异步任务,如果宏任务和微任务队列不为空,则以微任务->宏任务的过程执行。由于主线程不断的重复获得任务、执行任务、再获取任务、再执行,循环往复,所以叫事件循环,我是真讨厌循环。
宏任务内包裹微任务的形式中,
setInterval(()=>{
//code1..
new Promise(resolve =>{
resolve();
}).then(()=>{
// code2..
});
},timeout);
主线程从宏任务队列中获取到setInterval,执行后生成一个微任务,加入微任务队列,但没结束,又把setInterval加入宏任务队列,当timeout时间比较短的时候,就出问题了,可能上一条Promise没执行结束,下一条setInterval已经生成了新的微任务,这就造成顺序混乱了。