JavaScript的sleep实现--Javascript异步编程学习
一、原始需求
最近在做百度前端技术学院的练习题,有一个练习是要求遍历一个二叉树,并且做遍历可视化即正在遍历的节点最好颜色不同
二叉树大概长这个样子:

以前序遍历为例啊,
每次访问二叉树的节点加个sleep就好了?
笔者写出来是这样的:
let root = document.getElementById('root-box');
function preOrder (node) {
if (node === undefined) {
return;
}
node.style.backgroundColor = 'blue';//开始访问
sleep(500);
node.style.backgroundColor = '#ffffff';//访问完毕
preOrder(node.children[0]);
preOrder(node.children[1]);
}
document.getElementById('pre-order').addEventListener('click', function () {
preOrder(root);
});
问题来了,JavaScript里没有sleep函数!
二、setTimeout实现
了解JavaScript的并发模型 EventLoop 的都知道JavaScript是单线程的,所有的耗时操作都是异步的
可以用setTimeout来模拟一个异步的操作,用法如下:
setTimeout(function(){ console.log('异步操作执行了'); },milliSecond);
意思是在milliSecond毫秒后console.log会执行,setTimeout的第一个参数为回调函数,即在过了第二个参数指定的时间后会执行一次。

如上图所示,Stack(栈)上是当前执行的函数调用栈,而Queue(消息队列)里存的是下一个EventLoop循环要依次执行的函数。
实际上,setTimeout的作用是在指定时间后把回调函数加到消息队列的尾部,如果队列里没有其他消息,那么回调会直接执行。即setTimeout的时间参数仅表示最少多长时间后会执行。
更详细的关于EventLoop的知识就不再赘述,有兴趣的可以去了解关于setImmediate和Process.nextTick以及setTimeout(f,0)的区别
据此写出了实际可运行的可视化遍历如下:
let root = document.getElementById('root-box');
let count = 1;
//前序
function preOrder (node) {
if (node === undefined) {
return;
}
(function (node) {
setTimeout(function () {
node.style.backgroundColor = 'blue';
}, count * 1000);
})(node);
(function (node) {
setTimeout(function () {
node.style.backgroundColor = '#ffffff';
}, count * 1000 + 500);
})(node);
count++;
preOrder(node.children[0]);
preOrder(node.children[1]);
}
document.getElementById('pre-order').addEventListener('click', function () {
count = 1;
preOrder(root);
});
可以看出我的思路是把遍历时的颜色改变全部变成回调,为了形成时间的差距,有一个count变量在随遍历次数递增。
这样看起来是比较清晰了,但和我最开始想像的sleep还是差别太大。
sleep的作用是阻塞当前进程一段时间,那么好像在JavaScript里是很不恰当的,不过还是可以模拟的
三、Generator实现
在学习《ES6标准入门》这本书时,依稀记得generator函数有一个特性,每次执行到下一个yield语句处,yield的作用正是把cpu控制权交出外部,感觉可以用来做sleep。
写出来是这样:
let root = document.getElementById('root-box');
function* preOrder (node) {
if (node === undefined) {
return;
}
node.style.backgroundColor = 'blue';//访问
yield 'sleep';
node.style.backgroundColor = '#ffffff';//延时
yield* preOrder(node.children[0]);
yield* preOrder(node.children[1]);
}
function sleeper (millisecond, Executor) {
for (let count = 1; count < 33; count++) {
(function (Executor) {
setTimeout(function () {
Executor.next();
}, count * millisecond);
})(Executor);
}
}
document.getElementById('pre-order').addEventListener('click', function () {
let preOrderExecutor = preOrder(root);
sleeper(500, preOrderExecutor);
});
这种代码感觉很奇怪,像是为了用generator而用的(实际上也正是这样。。。),相比于之前的setTimeout好像没什么改进之处,还是有一个count在递增,而且必须事先知道遍历次数,才能引导generator函数执行。问题的关键在于让500毫秒的遍历依次按顺序执行才是正确的选择。
四、Generator+Promise实现
为了改进,让generator能够自动的按照500毫秒执行一次,借助了Promise的resolve功能。使用thunk函数的回调来实现应该也是可以的,不过看起来Promise更容易理解一点
思路就是,每一次延时是一个Promise,指定时间后resolve,而resolve的回调就将Generator的指针移到下一个yield语句处。
let root = document.getElementById('root-box');
function sleep (millisecond) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve('wake');
}, millisecond);
});
}
function* preOrder (node) {
if (node === undefined) {
return;
}
node.style.backgroundColor = 'blue';//访问
yield sleep(500);//返回了一个promise对象
node.style.backgroundColor = '#ffffff';//延时
yield* preOrder(node.children[0]);
yield* preOrder(node.children[1]);
}
function executor (it) {
function runner (result) {
if (result.done) {
return result.value;
}
return result.value.then(function (resolve) {
runner(it.next());//resolve之后调用
}, function (reject) {
throw new Error('useless error');
});
}
runner(it.next());
}
document.getElementById('pre-order').addEventListener('click', function () {
let preOrderExecutor = preOrder(root);
executor(preOrderExecutor);
});
看起来很像原始需求提出的sleep的感觉了,不过还是需要自己写一个Generator的执行器
五、Async实现
ES更新的标准即ES7有一个async函数,async函数内置了Generator的执行器,只需要自己写generator函数即可
let root = document.getElementById('root-box');
function sleep (millisecond) {
return new Promise(function (resovle, reject) {
setTimeout(function () {
resovle('wake');
}, millisecond);
});
}
async function preOrder (node) {
if (node === undefined) {
return;
}
let res = null;
node.style.backgroundColor = 'blue';
await sleep(500);
node.style.backgroundColor = '#ffffff';
await preOrder(node.children[0]);
await preOrder(node.children[1]);
}
document.getElementById('pre-order').addEventListener('click', function () {
preOrder(root);
});
大概只能做到这一步了,sleep(500)前面的await指明了这是一个异步的操作。
不过我更喜欢下面这种写法:
let root = document.getElementById('root-box');
function visit (node) {
node.style.backgroundColor = 'blue';
return new Promise(function (resolve, reject) {
setTimeout(function () {
node.style.backgroundColor = '#ffffff';
resolve('visited');
}, 500);
});
}
async function preOrder (node) {
if (node === undefined) {
return;
}
await visit(node);
await preOrder(node.children[0]);
await preOrder(node.children[1]);
}
document.getElementById('pre-order').addEventListener('click', function () {
preOrder(root);
});
不再纠结于sleep函数的实现了,visit更符合现实中的情景,访问节点是一个耗时的操作。整个代码看起来清晰易懂。
经过这次学习,体会到了JavaScript异步的思想,所以,直接硬套C语言的sleep的概念是不合适的,JavaScript的世界是异步的世界,而async出现是为了更好的组织异步代码的书写,思想仍是异步的
在下初出茅庐,文章中有什么不对的地方还请不吝赐教
参考文献:
1、《ES6标准入门》
2、JavaScript并发模型与Event Loop:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
JavaScript的sleep实现--Javascript异步编程学习的更多相关文章
- javascript异步编程学习及实例
所谓异步就是指给定了一串函数调用a,b,c,d,各个函数的执行完结返回过程并不顺序为a->b->c->d(这属于传统的同步编程模式),a,b,c,d调用返回的时机并不确定. 对于js ...
- C# 异步编程学习(一)
异步 编程 可在 等待 某个 任务 完成时, 避免 线程 的 占用, 但要 想 正确地 实现 编程, 仍然 十分 伤脑筋. . NET Framework 中, 有三种 不同 的 模型 来 简化 异步 ...
- ES6/7 异步编程学习笔记
前言 在ES6的异步函数出现之前,Js实现异步编程只有settimeout.事件监听.回调函数等几种方法 settTmeout 这种方法常用于定时器与动画的功能,因为其本质上其实是浏览器的WebAPI ...
- 轻松学习JavaScript二十二:DOM编程学习之节点操作
DOM编程不只能够查找三种节点,也能够操作节点.那就是创建,插入,删除.替换和复制节点.先来看节点 操作方法: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQ ...
- C#异步编程学习笔记之-async和await(续)
书接上文,本篇主要记录的内容要点:1.针对async和await在实际应用中的使用方式:2.异步方法返回值(有返回值和无返回值)的两种情况: 示例一(无返回值): using System; usin ...
- C#异步编程学习笔记之-async和await
一.异步方法介绍(async和await):如果使用async修饰符将某种方法指定为异步方法,即启用以下两种功能.1.标记的异步方法可以使用await来指定暂停点.await运算符通知编译器异步方法: ...
- .NET 4.5 Task异步编程学习资料
参考资料: 1. http://www.cnblogs.com/heyuquan/archive/2013/04/18/3028044.html
- 我了解到的JavaScript异步编程
一. 一道面试题 前段时间面试,考察比较多的是js异步编程方面的相关知识点,如今,正好轮到自己分享技术,所以想把js异步编程学习下,做个总结. 下面这个demo 概括了大多数面试过程中遇到的问题: f ...
- JavaScript异步编程(2)- 先驱者:jsDeferred
JavaScript当前有众多实现异步编程的方式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来自于CommonJS小组的努力:Promise/A+规范. 研究javascri ...
随机推荐
- spring boot高性能实现二维码扫码登录(中)——Redis版
前言 本打算用CountDownLatch来实现,但有个问题我没有考虑,就是当用户APP没有扫二维码的时候,线程会阻塞5分钟,这反而造成性能的下降.好吧,现在回归传统方式:前端ajax每隔1秒或2秒发 ...
- poj-1045(数学不好怪我咯)
Description Consider the AC circuit below. We will assume that the circuit is in steady-state. ...
- linux --> 获取进程执行时间
获取进程执行时间 一.时间概念 在linux下进行编程时,可能会涉及度量进程的执行时间.linux下进程的时间值分三种: 时钟时间(real time):指进程从开始执行到结束,实际执行的时间. 用户 ...
- Lucene 4.4.0中常用的几个分词器
一.WhitespaceAnalyzer 以空格作为切词标准,不对语汇单元进行其他规范化处理.很明显这个实用英文,单词之间用空格. 二.SimpleAnalyzer 以非字母符来分割文本信息,并将语汇 ...
- 爬虫(scrapy--豆瓣TOP250)
# -*- coding: utf-8 -*- import scrapy from douban_top250.items import DoubanTop250Item class MovieSp ...
- oracle 分析函数中 keep关键字的使用
语法 min | max(column1) keep (dense_rank first | last order by column2) over (partion by column3); 另外f ...
- .Net的垃圾回收机制(GC)之拙见——托管类型的垃圾回收
各种语言的垃圾回收在IT界噪的沸沸扬扬,有极大的优化同时也有瓶颈. 而在.Net中的垃圾回收机制又是怎样的呢? 众所知周,.Net中的垃圾回收机制是由.Net Framework托管的,带给开发者最大 ...
- 初学MySQL基础知识笔记--02
查询部分 1> 查询数据中所有数据:select * from 表名 2> 查询数据中某项的数据:eg:select id,name from students; 3> 消除重复行: ...
- JavaScript(第二十五天)【事件绑定及深入】
事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2级模型).现代事件绑定在传统绑定上提供了更强大更方便的功能. 一.传统事件绑定的问题 传统事件绑定有内联模型 ...
- 用C语言实现QQ刷屏
我在百度传课上录制了一个用C语言实现刷屏的视频,有兴趣的可以移步这边:https://chuanke.baidu.com/6658388-238008.html