一、原始需求

最近在做百度前端技术学院的练习题,有一个练习是要求遍历一个二叉树,并且做遍历可视化即正在遍历的节点最好颜色不同

二叉树大概长这个样子:

以前序遍历为例啊,

每次访问二叉树的节点加个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异步编程学习的更多相关文章

  1. javascript异步编程学习及实例

    所谓异步就是指给定了一串函数调用a,b,c,d,各个函数的执行完结返回过程并不顺序为a->b->c->d(这属于传统的同步编程模式),a,b,c,d调用返回的时机并不确定. 对于js ...

  2. C# 异步编程学习(一)

    异步 编程 可在 等待 某个 任务 完成时, 避免 线程 的 占用, 但要 想 正确地 实现 编程, 仍然 十分 伤脑筋. . NET Framework 中, 有三种 不同 的 模型 来 简化 异步 ...

  3. ES6/7 异步编程学习笔记

    前言 在ES6的异步函数出现之前,Js实现异步编程只有settimeout.事件监听.回调函数等几种方法 settTmeout 这种方法常用于定时器与动画的功能,因为其本质上其实是浏览器的WebAPI ...

  4. 轻松学习JavaScript二十二:DOM编程学习之节点操作

    DOM编程不只能够查找三种节点,也能够操作节点.那就是创建,插入,删除.替换和复制节点.先来看节点 操作方法: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQ ...

  5. C#异步编程学习笔记之-async和await(续)

    书接上文,本篇主要记录的内容要点:1.针对async和await在实际应用中的使用方式:2.异步方法返回值(有返回值和无返回值)的两种情况: 示例一(无返回值): using System; usin ...

  6. C#异步编程学习笔记之-async和await

    一.异步方法介绍(async和await):如果使用async修饰符将某种方法指定为异步方法,即启用以下两种功能.1.标记的异步方法可以使用await来指定暂停点.await运算符通知编译器异步方法: ...

  7. .NET 4.5 Task异步编程学习资料

    参考资料: 1. http://www.cnblogs.com/heyuquan/archive/2013/04/18/3028044.html

  8. 我了解到的JavaScript异步编程

    一. 一道面试题 前段时间面试,考察比较多的是js异步编程方面的相关知识点,如今,正好轮到自己分享技术,所以想把js异步编程学习下,做个总结. 下面这个demo 概括了大多数面试过程中遇到的问题: f ...

  9. JavaScript异步编程(2)- 先驱者:jsDeferred

    JavaScript当前有众多实现异步编程的方式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来自于CommonJS小组的努力:Promise/A+规范. 研究javascri ...

随机推荐

  1. PHP 简单的加密解密方法

    本算法的基础:给定字符A B,A^B=C,C^B=A,即两次异或运算可得到原字符.实现代码如下: /** * @desc加密 * @param string $str 待加密字符串 * @param ...

  2. 2016最热门的PHP框架

    每个PHP框架都拥有各自独特的地方.同时PHP语言已经获得了巨大的认同并且成为了世界上最通用的服务器脚本语言.PHP也俨然成为了最容易学习的web动态开发语言.在PHP发展的同时,PHP框架也迅速崛起 ...

  3. Java 后端微信支付demo

    Java 后端微信支付demo 一.导入微信SDK 二.在微信商户平台下载证书放在项目的resources目录下的cert文件夹下(cert文件夹需要自己建) 三.实现微信的WXPayConfig接口 ...

  4. 学习ASP.NET Core Razor 编程系列三——创建数据表及创建项目基本页面

    一.创建脚本工具并执行初始迁移 在本节中,您将使用包管理控制台(PMC)来更新数据库: •添加VisualStudio Web代码生成包.这个包是运行脚本引擎所必需的. • 执行Add-Migrati ...

  5. Redis主从集群的Sentinel配置

    http://www.cnblogs.com/LiZhiW/p/4851631.html

  6. 记录一则ASM实例阻塞,rbal进程异常的案例

    1.故障现象描述 2.确认故障现象 3.排查ASM层面 4.解决问题 1.故障现象描述 环境:AIX 7.1 + Standalone Oracle 11.2.0.4 现象:客户反映某11g版本的AD ...

  7. Algorithm --> 判读是否是子树

    问题 判断一棵树是否是另一棵树的子树,如图 思路 问题分两步: 找值相同的根结点(遍历解决) 判断两结点是否包含(递归:值.左孩子.右孩子分别相同) 树节点定义 struct TreeNode { i ...

  8. clumsy模拟客户端网络差的场景的使用

    1.为什么需要模拟客户端网络差的情况? a. 研发环境的网络配置一般较高,网络响应快,不容易出现网络故障,但是客户使用的网络环境千差万别,不排除使用过程中遇到网络故障的情况. b.程序有些时候是多线程 ...

  9. 为什么说android UI操作不是线程安全的

    转载于:http://blog.csdn.net/lvxiangan/article/details/17218409#t2 UI线程及Android的单线程模型原则 使用Worker线程 Commu ...

  10. 高级软件工程2017第5次作业—— 团队项目:需求改进&系统设计

    Deadline:2017-10-23(周一) 21:00pm 注:以下内容参考 集大作业 1.评分规则: 按时交 - 有分,检查的项目包括后文的四个方面 需求&原型改进 - 20分 系统设计 ...