JavaScript 原生不支持 sleep 函数,本文详细解析六种实现方案。包括基于 Date 的死循环阻塞、Promise 链式调用、Generator 配合 co、ES7 async/await、child_process 子进程同步以及 npm sleep 包。重点对比各方案在阻塞主线程、代码可读性及执行效率上的差异,帮助开发者根据场景选择最佳延时策略。
本文介绍 JavaScript 中实现 sleep(延时)功能的多种常见方法,供开发者参考。
sleep 函数定义与必要性
一、什么是 sleep 函数?
首先探讨 sleep 函数的定义与作用。
sleep 是一种用于使程序暂停执行指定时间的函数,主要起到延时效果。
例如:
console.log('1');
sleep(5000);
console.log('2');
期望在控制台输出数字 1 后,等待 5 秒再输出数字 2。
需要注意的是,上述伪代码无法直接运行,因为 JavaScript 原生环境中并不存在 sleep 方法。
因此,本文将重点介绍几种在 JavaScript 中模拟实现 sleep 功能的技术方案。
二、为什么需要 sleep?
读者可能会疑惑:既然可以使用 setTimeout 实现定时任务,为何还要专门实现 sleep?
因为 setTimeout 基于回调函数机制,在处理多个连续延时任务时,容易导致严重的回调嵌套问题(Callback Hell)。
console.time('runTime:');
setTimeout(function(){
console.log('1')
setTimeout(function(){
console.log('2');
setTimeout(function(){
console.log('3');
console.timeEnd('runTime:');
}, 2000);
}, 3000);
}, 2000);
// 1
// 2
// 3
// runTime:: 7013.104ms
为了解决上述嵌套问题,我们寻求一种更优雅的实现方式。
sleep(2000);
console.log('1');
sleep(3000);
console.log('2');
sleep(2000);
console.log('3');
...
三、sleep 函数的具体实现
接下来,我们将分别演示几种不同的 sleep 实现方法。
基于同步阻塞的实现方案
1. 基于 Date 对象实现
该方法通过死循环不断比对当前时间与目标时间,直到超时才退出循环。
function sleep(time){
var timeStamp = new Date().getTime();
var endTime = timeStamp + time;
while(true){
if (new Date().getTime() > endTime){
return;
}
}
}
console.time('runTime:');
sleep(2000);
console.log('1');
sleep(3000);
console.log('2');
sleep(2000);
console.log('3');
console.timeEnd('runTime:');
// 1
// 2
// 3
// runTime:: 7004.301ms
缺点:
此代码并未真正让线程休眠,而是通过高强度的 CPU 计算占用资源,导致 CPU 无法处理其他任务。
其副作用是,在 sleep 执行期间,所有其他任务(包括 DOM 渲染)都会被暂停。
因此,在 sleep 过程中程序会处于“假死”状态,无法执行其他任何任务。
基于异步控制的实现方案
2. 基于 Promise 的 sleep
为了解决 AJAX 回调嵌套问题,jQuery 1.8 之后引入了 Promise 支持。但单纯使用 Promise 只是将纵向嵌套转化为横向嵌套。
最终代码结构如下:
function sleep(time){
return new Promise(function(resolve){
setTimeout(resolve, time);
});
}
console.time('runTime:');
console.log('1');
sleep(1000).then(function(){
console.log('2');
sleep(2000).then(function(){
console.log('3');
console.timeEnd('runTime:');
});
});
console.log('a');
// 1
// a
// 2
// 3
// runTime:: 3013.476ms
这种方式与之前的 setTimeout 嵌套在本质上没有区别,代码可读性依然较差。
我们进一步优化,利用 ES6 的 Generator 函数来重构上述示例。
3. 基于 Generator 函数的 sleep
通过 Generator 函数执行 sleep,并配合 co 库进行自动执行。
参考代码如下:
var co = require('co');
function sleep(time){
return new Promise(function(resolve){
setTimeout(resolve, time);
});
}
var run = function* (){
console.time('runTime:');
console.log('1');
yield sleep(2000);
console.log('2');
yield sleep(1000);
console.log('3');
console.timeEnd('runTime:');
}
co(run);
console.log('a');
// 1
// a
// 2
// 3
// runTime:: 3004.935ms
可以看出,整体代码结构清晰,不再存在嵌套关系,阅读体验较好。
此外,该执行过程不会导致假死,也不会阻塞其他任务的运行。
然而,该方法需要额外引入 co 执行器,因此仍存在一定瑕疵。
这并非最终解决方案,因为 ES7 带来了更先进的实现方式。
ES7 async 与子进程方案
4. 基于 async 函数的 sleep
ES7 新增的 async 函数自带执行器,因此无需借助 co 即可实现 sleep。
参考代码如下:
function sleep(time){
return new Promise((resolve) => setTimeout(resolve, time));
}
async function run(){
console.time('runTime:');
console.log('1');
await sleep(2000);
console.log('2');
await sleep(1000);
console.log('3');
console.timeEnd('runTime:');
}
run();
console.log('a');
// 1
// a
// 2
// 3
// runTime:: 3009.984ms
其运行效果与之前基于 Generator 的方案一致。
5. 使用 child_process(子进程)实现 sleep 函数
前几种方法较为简单,接下来介绍一种相对复杂的实现方式。
其原理是将 sleep 逻辑放入子进程中执行,从而不影响主进程,参考代码如下:
var childProcess = require('child_process');
var nodeBin = process.argv[0];
function sleep(time) {
childProcess.execFileSync(nodeBin, ['-e', 'setTimeout(function() {}, ' + time + ');']);
// childProcess.spawnSync(nodeBin, ['-e', 'setTimeout(function() {}, ' + time + ');']);
}
console.time('runTime:');
console.log('1');
sleep(1000);
console.log('2');
sleep(2000);
console.log('3');
console.timeEnd('runTime:');
// 1
// 2
// 3
// runTime:: 3579.093ms
上述代码通过 childProcess 对象的 execFileSync 或 spawnSync 方法创建一个同步子进程。
在该同步子进程中执行定时器,待定时器执行完毕后回收子进程,主程序继续向下执行。
第三方 npm 包方案
6. 使用 npm sleep 包
除了手动实现,npm 上已有许多现成的相关 JS 包可供使用。
我们来看看第三方库 sleep 的实现方式。
var sleep = require('sleep');
console.log('1');
console.time('runTime:');
sleep.sleep(2); //休眠2秒钟
console.log('2');
sleep.msleep(1000); //休眠1000毫秒
console.log('3');
sleep.usleep(1000000) //休眠1000000微秒 = 1秒
console.log('4');
console.timeEnd('runTime:');
// 1
// 2
// 3
// 4
// runTime:: 4014.455ms
该 sleep 包由 C++ 编写并扩展至 Node.js,性能强劲,也是一个不错的选择。
以上介绍了 sleep 的六种简单实现方式。欢迎指出问题,共同交流进步。
感兴趣的朋友可以使用在线 HTML/CSS/JavaScript 代码运行工具:http://tools.jb51.net/code/HtmlJsRun 测试上述代码的运行效果。
更多关于 JavaScript 相关内容可查看本站专题:《JavaScript常用函数技巧汇总》、《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》及《JavaScript数学运算用法总结》。
希望本文所述对大家 JavaScript 程序设计有所帮助。