刚下班,累了,但是这个问题其实困扰我很多年了,今晚又偶然想起,决定把这个问题拿出来说一说,但是我就不打算给出具体代码了,因为,凡是优雅而彻底的方法他如果不是原生支持的那就一定是复杂的实现,我现在没有精力去写那么复杂的东西。但是,提供一个思路,有兴趣的朋友自己去实现也可以。
首先我们来看看为什么js原生不支持像python的time.sleep()这样的方法,因为js是单线程的嘛,你一sleep他就阻塞了。当然,js也给出了一些代替的机制,那就是setTimeout和setInterval,还有写游戏的人都喜欢的requestAnimationFrame,这三者都不是真正意义上的time.sleep,而是定时任务,在任务执行完毕之后去执行下一个函数/事件。
我们可以这样写:
function1();
setTimeout(function2,1000)
但是如果有多层,就会变成:
function1();
setTimeout(function(){
function2();
setTimeout(function3,1000);
},1000);
其实如果需求不是很复杂,用这种看起来很蛋疼的写法也是可以的。如果想做得精细一点,可以自己封装一个函数,伪代码如下:
var clock = new Clock();
clock.add(function1,0)
clock.add(function2,1000)
clock.add(function3,2000)
clock.run()
至于怎么实现这个很简单的clock,其实就是结束事件链式调用而已,就不赘述了。这个方案比上一个优雅一些,但是依然和python java这种自带sleep函数的语言差距明显。
如果可以使用外部力量(也就是不纯靠js)的话,其实也有一些解决方案,比如说使用flash、javalet的sleep方法,或者用ajax配合后端的php/java/python/golang/c++等等做个异步,都可以,这个网上都有教程,我也懒得展开,但终究觉得怪怪的。知乎上还有人扯远扯到了fibjs,它底层相当于自己重构了嘛,当然可以随便sleep了,顺便一提,这是个很好的后端协程应用开发框架,值得安利一下,有兴趣的话建议百度一下做了解。
有的人可能会说可以用es6的async和promise大法来解决,嗯,像这样:
function sleep(millisecond) {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, millisecond)
})
}
async function test() {
const start = new Date().getTime();
console.log("执行开始",start);
await sleep(3000);
console.log("执行结束",new Date().getTime() - start)
}
test();
如果是只面向现代浏览器的话,其实这样写无可厚非了,甚至可以说是最佳解决方案了,但是如果要兼容那些破烂老旧浏览器,这招是不是还是要凉凉?
那要不,这样?
function sleep(delay) {
var start = new Date().getTime();
while (new Date().getTime() < start + delay)
;
}
这样在火狐谷歌等浏览器需要大概跑掉20-60%的CPU,如果是老爷机,可能会让你卡到怀疑人生,如果是IE8这种石器时代的浏览器,想都不要想,直接崩给你看。当然,部分网站为了省事确实是用这种代码写的。毕竟,燃烧用户的CPU关我开发者什么事?
但是,我总觉得,不够优雅,以上这些实现,除了使用es6语法的高端实现以外,基本上都是比较折衷的方案。
说到底,还是js这种语言先天性的机制不支持time.sleep()这种语法吧。
等等,你说先天性?
那如果在解析器层面上让他支持呢?
转念一想,可行啊,应该是可行的,不,肯定是可行的,既然有大佬可以用js写系统,用js写lua、python脚本解析器,那当然也可以用js写js代码解析器了。
而且,我们需要的不复杂,
只需要将形如:
fun1()
sleep(500)
fun2()
这样的代码翻译成:
var clock = new Clock();
clock.add(fun1,0)
clock.add(fun2,500)
clock.run()
这样的代码不就可以了?关于这个clock还可以有更优雅的实现,读者朋友自己想一下都可以任意扩展。
聪明如你肯定一下子就想到了eval()和正则表达式了,如果追求比较简单,是可以将整个业务逻辑的js读成大字符串,再对这个大字符串进行处理后,再用eval()函数去跑这个大字符串的。
但是,js本身的函数稍微复杂了一些
有这样的:fun1()
有这样的:(function(){fun2()})()
有这样的:fun3(function(){ fun4() })
。。。
还要考虑嵌套的问题、在循环体之间的sleep问题等等,用简单的正则表达式去匹配出来,怕不是难以搞定而且容易出bug。
那么,换个思路,直接来解析js,像个真正的解析器一样,把文本解析成AST,再对AST进行系列加工和封装。
可参考:https://zhuanlan.zhihu.com/p/34191831
这个东西搞起来绝对是个大工程,精力有限,我没办法搞了。但是至少留一个思路给有心人吧。解析器本身不需要太过复杂,只要对解析出来的执行语句进行稍微包装,最终还是变成字符串交由我们伟大的eval函数去执行。性能上可能会稍损,但影响不会很严重。我认为这才是彻底的、优雅的、终极的解决js不支持time.sleep的方法。
通过对js的二次解析,还可以赋予js更多的语法和扩展性,甚至将js彻底变成另一门语言。我现在没精力做这么大的事情了。思路留给大家吧。
如果实现了,那就真的是赋予js新的活力了,绝不只是解决一个比较无关疼痒的sleep这么简单。