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 ...
随机推荐
- delphi 数组复制利用CopyMemory 最为完美
在各网站的文章里面,见复制数据的方法中,有move的,有system.copy的,而要实际应用中,这两种方法,并不是很完美,会遇到一些问题,比如copy在记录里面的复制时,编译都过不去,而CopyMe ...
- iOS 提交审核报错 ERROR ITMS-90087解决办法
ERROR ITMS-: "Unsupported Architectures. The executable for yht.temp_caseinsensitive_rename.app ...
- 快速排序及优化(Java实现)
普通快速排序 找一个基准值base,然后一趟排序后让base左边的数都小于base,base右边的数都大于等于base.再分为两个子数组的排序.如此递归下去. public class QuickSo ...
- 【CSS】 CSS 定位
css 定位和浮动 *******本章大量内容copy自w3school********* 定位对于html界面还是很重要的,因为定位会直接影响到用户的视图.对于css而言,定位也比较灵活. 浮动是一 ...
- Spring Boot 2.0(五):Docker Compose + Spring Boot + Nginx + Mysql 实践
我知道大家这段时间看了我写关于 docker 相关的几篇文章,不疼不痒的,仍然没有感受 docker 的便利,是的,我也是这样认为的,I know your felling . 前期了解概念什么的确实 ...
- Anagram
Anagram poj-1256 题目大意:给你n个字符串,求每一个字符串所有字符的全排列,按照顺序输出所有全排列. 注释:每一个字符长度小于13,且字符排序的顺序是:A<a<B<b ...
- java.text.DateFormat 多线程并发问题
在日常开发中,java.text.DateFormat 应该算是使用频率比较高的一个工具类,经常会使用它 将 Date 对象转换成字符串日期,或者将字符串日期转化成 Date 对象.先来看一段眼熟的代 ...
- Python中安装模块的方法
1.*nix系统上有一个地方专门有一个地方来放置安装的Python模块 比如在Mac上,这个目录的路径为: /usr/lib/python2.7 将要安装的文件拷贝到这里即可 2.下载模块包,解压后, ...
- Spring学习笔记(1)
激发pojo的潜能,不会让pojo类继承实现或导入与Spring API相关的任何东西 那么spring怎么装配pojo呢-------->依赖注入(最大好处:松耦合) 耦合具有两面性: 一方面 ...
- localhost访问不了的解决方法
c:\windows\system32\drivers\etc\hosts 用记事本打开,加入一行 127.0.0.1 localhost