异步执行之setTimeout
如果要对一个十分巨大的数组进行遍历,进行复杂的逻辑计算,再将数据渲染到浏览器,通常会这样书写代码。
for (let i = 0; i < 2000; i++) {
process(i) //逻辑处理
}
但是,当页面刷新的时候,如果数据量很大,或者逻辑运算复杂,会发现页面要等待很久才能加载出来,这是因为在process
逻辑处理的时候,页面一直处于阻塞状态,导致迟迟不能显示。
要解决阻塞的问题,就是要想办法让出UI线程的控制权,使得UI可以更新。我们可以让这些数据的处理处于异步的过程中,从而不会影响页面UI主线程导致阻塞。
setTimeout
可以用来实现代码异步的目的,举个例子:
setTimeout(() => {
console.log('world')
})
console.log('hello')
最终会先输出hello
,再输出world
,这是因为setTimeout
中的任务,js会将其添加到任务队列的末尾,当队列空闲时才会执行,所以会在最后打印world
,在之前关于setTimeout这篇文章中也有介绍。
所以可以将最上面的代码改写为如下方式(由于setTimeout
定时器的精度问题,一般会将延时设置为25毫秒)
let array = []
for (let i = 0; i < 2000; i++) {
array.push(i)
}
let todo = array.concat() //数组拷贝
setTimeout(function () {
console.log(todo.shift())
if (todo.length > 0) {
setTimeout(arguments.callee, 25)
}
}, 25)
改写后,数据循环处理代码不会阻塞浏览器渲染了。但是,问题又来了,上面每处理一次数据都要延迟25毫秒,假如每处理数组中的一项需要1毫秒,如果每个定时器只处理一项,且在两次处理之间产生25毫秒的延迟,那么2000条数据就是2000*(25+1) = 52000
,也就是52秒。这更不能容忍了,由于拆分了太多零散的定时任务,导致了延时过长,要解决这个问题,可以考虑将它们进行分组,比如,每100条数据进行一个分组,那么2000/100*25 + 2000=2500
,那么2000条的数据处理延时就变成了2500毫秒,相比之前有了很大的提升。
let array = []
for (let i = 0; i < 1000; i++) {
array.push(i)
}
const chunkItems = _.chunk(array, 100) //lodash方法
chunkItems.forEach(chunkItem => {
let todo = chunkItem.concat()
setTimeout(function () {
console.log(todo.shift())
if (todo.length > 0) {
setTimeout(arguments.callee, 25)
}
}, 25)
})
改写之后,相比之前,渲染速度有了很大的提升,但是,还有改进的余地,以100条数据进行拆分也不一定十分合理,我们可以用代码运行时间检测来判断是否需要将逻辑执行加入setTimeout
队列,以代码运行50毫秒时间为基准,如果数组单条逻辑处理执行时间超过50毫秒,就添加到队列,如果不到就直接执行。
function timedProcessArray(items, process, callback) {
const todo = items.concat()
setTimeout(function () {
const start = +new Date()
do {
process(todo.shift())
} while (todo.length > 0 && (+new Date() - start < 50))
if (todo.length > 0) {
setTimeout(arguments.callee, 25)
} else {
callback(items)
}
})
}
在函数中添加了一个do-while循环,它在每个数组条目处理完成后检测执行时间。
- 本博客所有文章除特别声明外,均可转载和分享,转载请注明出处!
- 本文地址:https://www.leevii.com/?p=1187