一、原始需求

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

二叉树大概长这个样子:

以前序遍历为例啊,

每次访问二叉树的节点加个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. windows下安装Virtualenvwrapper

    windows下安装Virtualenvwrapper 我们可以使用Virtualenvwrapper来方便地管理python虚拟环境,但是在windows上安装的时候.....直接 install  ...

  2. vuex2中使用mapMutations/mapActions报错解决方法 BabelLoaderError: SyntaxError: Unexpected token

    在尝鲜vuex2时,发现vuex2增加了 mapGetters 和 mapActions 的方法,借助stage2的 Object Rest Operator 特性,可以写出下面代码:methods: ...

  3. AndroidStudio R 文件标红

    一种不常见的问题 AndroidStudio 文件大小会有一定的限制,超过一定大小将无法解析.大型的Android项目容易出现这个问题. 可以按照下面的步骤解决这个问题: 在AndroidStudio ...

  4. 【Python】 获取MP3信息replica

    replica 初衷是想要整理iphone中的音乐.IOS(我自己的手机还是IOS8.3,新版本的系统可能有变化了)自带的音乐软件中所有音乐文件都存放在/var/mobile/Media/iTunes ...

  5. MySQL使用和操作总结

    简介 MySQL是一种DBMS,即它是一种数据库软件.DBMS可分为两类:一类是基于共享文件系统的DBMS,另一类是基于客户机——服务器的DBMS.前者用于桌面用途,通常不用于高端或更关键应用. My ...

  6. java中有关流操作的类和接口

    一.java操作l流有关的类和接口 1.File 文件类 2.RandomAccessFile 随机存储文件类 3.InputStream 字节输入流 4.OutputStream 字节输出流 5.R ...

  7. 将 Shiro 作为应用的权限基础 二:基于SpringMVC实现的认证过程

    认证就是验证用户身份的过程.在认证过程中,用户需要提交实体信息(Principals)和凭据信息(Credentials)以检验用户是否合法.最常见的“实体/凭证”组合便是“用户名/密码”组合. 一. ...

  8. 关于HTML使用ComDlg ActiveX 无法弹出相应对话框的问题1

    最近发现,开发的Web应用在客户的某些IE(8,9,11)中弹出不了Windows的字体对话框. 通过 F12 跟踪,错误代码是“-2146827850”,错误信息是“ 对象不支持ShowFont属性 ...

  9. Java 多线程并发编程之 Synchronized 关键字

    synchronized 关键字解析 同步锁依赖于对象,每个对象都有一个同步锁. 现有一成员变量 Test,当线程 A 调用 Test 的 synchronized 方法,线程 A 获得 Test 的 ...

  10. 如何从零开始学习区块链技术——推荐从以太坊开发DApp开始

    很多人迷惑于区块链和以太坊,不知如何学习,本文简单说了一下学习的一些方法和资源. 一. 以太坊和区块链的关系 从区块链历史上来说,先诞生了比特币,当时并没有区块链这个技术和名词,然后业界从比特币中提取 ...