第61条解释了异步API怎样帮助我们防止一段程序阻塞应用程序的事件队列。使用下面代码,可以很容易使一个应用程序陷入泥潭。

while(true){}

而且它并不需要一个无限循环来写一个缓慢的程序。代码需要时间来运行,而低效的算法或数据结构可能导致运行长时间的计算。
效率不是js唯一关注的。基于事件的编程的确强加了一些特殊的约束。为了保持客户端应用程序的高度交互性和确保所有传入的请求在服务器应用程序中得到充分的服务,保持事件循环的每个轮次尽可能短是至关重要。否则,事件队列会滞销,其增长速度会超过分发处理事件处理程序的速度。在浏览器环境中,一些代价高昂的计算也会导致糟糕的用户体验,因为一个页面的用户界面无响应多数是由于在运行js代码。
那么,如果你的应用程序需要执行代价高昂的计算你该怎么办呢?没有一个完全正确的答案,但有一些通用的技术可用。也许最简单的方法是使用像Web客户端平台的Worker API这样的并发机制。这对于需要搜索大量可移动距离的人工智能游戏是一个很好的方法。游戏可能以生成大量的专门计算移动距离的worker开始。

var ai=new Worker('ai.js');

这将使用ai.js源文件作为worker的脚本,产生一个新的线程独立的事件队列的并发执行线程。该worker运行在一个完全隔离的状态--没有任何应用程序对象的直接访问。但是,应用程序与worker之间可以通过发送形式为字符串的messages来交互。所以,每当游戏需要程序计算移动时,它会发送一个消息给worker。

var userMove=/* ... */;
ai.postMessage(JSON.stringify({userMove:userMove}));

postMessage的参数被作为一个消息增加到worker的事件队列中。为了处理worker的响应,游戏会注册一个事件处理程序。

ai.onmessage=function(event){
executeMove(JSON.parse(event.data).computerMove);
};

与此同时,源文件ai.js指示worker监听消息并执行计算下一步移动所需的工作。

self.onmessage=function(event){
var userMove=JSON.parse(event.data).userMove;
var computerMove=computeNextMove(userMove);
var message=JSON.stringify({
computerMove:computerMove
});
selft.postMessage(message);
};
function computeNextMove(userMove){
//...
}

不是所有的js平台都提供类似Worker这样的API。而且有时传递消息的开销可能会过于昂贵。另一种方法是将算法分解为多个步骤,每个步骤组成一个可管理的工作块。第48条中搜索社交网络图的工作表算法。

Member.prototype.inNetwork=function(other){
var visited={};
var worklist=[this];
while(worklist.length>0){
var member=worklist.pop();
if(member === other){
return true;
}
}
return false;
};

如果这段程序核心的while循环代价太过高昂,搜索工作很可能会以不可接受的时间运行而阻塞应用程序事件队列。即使我们可以使用Worker API,它也是昂贵或不方便实现的,因为它需要复制整个网络图的状态或在worker中存储网络图的状态,并总是使用消息传递来更新和查询网络。
幸运的是,这种算法被定义为一个步骤集的序列--while循环的迭代。可以通过增加一个回调参数将inNetwork转换为一个匿名函数,并像第64条讲述的,将while循环替换一个匿名的递归函数。

Member.prototype.inNetwork=function(other,callback){
var visited={};
var worklist=[this];
function next(){
if(worklist.length === 0){
callback(false);
return;
}
var member=worklist.pop();
if(member === other){
callback(true);
return;
}
setTimeout(next,0);
}
setTimeout(next,0);
};

这段代码的工作方式,为了替换while循环,这里写了一个局部的next函数,该函数执行循环中的单个迭代然后高度应用程序事件队列来异步运行下一次迭代。这使得在些期间已经发生的其他事件被处理后才继续下一次迭代。当搜索完成后,通过迭代的next来返回,从而有效地完成循环。
要调度迭代,我们使用多数js平台都可用的、通用的setTimeout API来注册next函数,使next函数经过一段最少时间(0毫秒)后运行。这具有几乎立刻将回调函数添加到事件队列上的作用。值得注意的是,虽然setTimeout有相对稳定的跨平台移植性,但通常还有更好的替代方案。例如,在浏览器环境中,最低的超时时间被压制为4毫秒,可以采用一种替代方案,使用postMessage立即压入一个事件。
如果应用程序事件队列的每个轮次中只执行算法的一个迭代。可以调整算法,自定义每个轮次中的迭代次数。这很容易实现,只须在next函数的主要部分的外围使用一个循环计数器。

Member.prototype.inNetwork=function(other,callback){
function next(){
for(var i=0;i<10;i++){
//...
}
setTimeout(next,0);
}
setTimeout(next,0);
};

提示

  • 避免在主事件队列中执行代价高昂的算法

  • 在支持Worker API的平台,该API可以用来在一个独立的事件队列中运行长计算程序

  • 在Worker API不可用或代价昂贵的环境中,考虑将计算程序分解到事件循环的多个轮次中

附录一:Worker

Worker是可以在后台运行的任务,它能够被轻松创建,还能向它的创建者发送消息。只要调用worker()构造函数,指定一个需要运行在worker线程内的脚本,就能创建一个worker。
注意:worker能够产生新的worker,前提是这些worker托管于相同的源内来作为它们的父页面。
Worker线程能够在不干扰UI的情况下执行任务。另外,它能够使用XMLHttpRequest来执行I/O操作,只不过XMLHttpRequest上的responseXML与channel两个属性值始终返回null。

线程安全

Worker接口会生成真正的操作系统级别的线程,如果你不小心,那么并发会对你的代码产生影响。对于web worker来说,与其他线程的通信点会被很小心的控制,这意味着你很难引发并发问题。你没有办法去访问非线程安全的组件或者是DOM,此外还需要序列化对象来与线程交互特定的数据。

worker语法

构造函数
Worker(in DOMString scriptURL);

参数
scriptURL
worker将要执行的脚本的URL。它必须遵守同源策略。
返回值
一个新的Worker对象。

方法
void postMessage(Object message[,sequence<Transferable> transferList]);

参数
message
传输给woker的对象;它将包含于传递给onmessage处理函数的事件对象中的data字段内。可以传递任意值或是经过结构拷贝算法处理过的js对象,即可以包含循环引用。
transferList
一个可选的对象数组,用于转让它们的所有权。如果一个对象的所有权被转让,那么它在原来的上下文内将不可使用,而只能在转让到的worker内可用

void terminate();

立即终止worker。该方法不会给worker留下任何完成操作的机会;就是简单的立即停止。

属性

onmessage EeventListner
一个事件监听函数,每当拥有message属性的MessageEvent从worker中冒泡出来时就会执行该函数。事件的data属性存有消息内容。

onerror EeventListner
一个事件监听函数,每当类型为error的ErrorEvent从worker中冒泡出来时就会执行该函数。

错误信息对象

  • message 一个可读性良好的错误信息

  • filename 产生错误的脚本文件名

  • lineno 发生错误时所在的脚本文件号

[Effective JavaScript 笔记]第65条:不要在计算时阻塞事件队列的更多相关文章

  1. [Effective JavaScript 笔记] 第4条:原始类型优于封闭对象

    js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //&q ...

  2. [Effective JavaScript 笔记] 第5条:避免对混合类型使用==运算符

    “1.0e0”=={valueOf:function(){return true;}} 是值是多少? 这两个完全不同的值使用==运算符是相等的.为什么呢?请看<[Effective JavaSc ...

  3. [Effective JavaScript 笔记]第27条:使用闭包而不是字符串来封装代码

    函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式 ...

  4. [Effective JavaScript 笔记]第28条:不要信赖函数对象的toString方法

    js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+ ...

  5. [Effective JavaScript 笔记]第67条:绝不要同步地调用异步的回调函数

    设想有downloadAsync函数的一种变种,它持有一个缓存(实现为一个Dict)来避免多次下载同一个文件.在文件已经被缓存的情况下,立即调用回调函数是最优选择. var cache=new Dic ...

  6. [Effective JavaScript 笔记]第53条:保持一致的约定

    对于api使用者来说,你所使用的命名和函数签名是最能产生普遍影响的决策.这些约定很重要具有巨大的影响力.它建立了基本的词汇和使用它们的应用程序的惯用法.库的使用者必须学会阅读和使用这些.一致的约定可以 ...

  7. [Effective JavaScript 笔记]第68条:使用promise模式清洁异步逻辑

    构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.t ...

  8. [Effective JavaScript 笔记]第46条:使用数组而不要使用字典来存储有序集合

    对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置 ...

  9. [Effective JavaScript 笔记]第45条:使用hasOwnProperty方法以避免原型污染

    之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangs ...

随机推荐

  1. 【BUG】wego购物分享系统未登陆分享宝贝时查看宝贝自动新增产品数据

    1.登录微购http://demo.wego360.com/站. 2.分享宝贝功能检索第三方平台商品数据. 1.登录微购http://demo.wego360.com/站. 2.分享宝贝功能检索第三方 ...

  2. Android基于XMPP的即时通讯2-文件传输

    本文是在上一篇博文Android基于XMPP的即时通讯1-基本对话的基础上,添加新的功能,文件传输 1.初始化文件传输管理类 public static FileTransferManager get ...

  3. [LintCode] Coins in a Line 一条线上的硬币

    There are n coins in a line. Two players take turns to take one or two coins from right side until t ...

  4. Hadoop.2.x_WordCount本地测试示例

    代码如下, 后备参考: package com.bigdata.hadoop.hdfs; import java.io.IOException; import org.apache.hadoop.co ...

  5. [转] - bashrc与profile的区别

    bashrc与profile的区别 要搞清bashrc与profile的区别,首先要弄明白什么是交互式shell和非交互式shell,什么是login shell 和non-login shell. ...

  6. SQL server 视图、范式

    视图 1.视图的概述       视图其实就是一条查询sql语句,用于显示一个或多个表或其他视图中的相关数据.视图将一个查询的结果作为一个表来使用,因此视图可以被看作是存储的查询 或一个虚拟表.视图来 ...

  7. linux下mysql的忘记root密码的解决办法

    因为放寒假家里没有宽带,便很少上网,前几天用手机进入自己的个人博客时竟然返回数据库不能连接的错误,吓我一跳,网站肯定被人黑了,但转头一想我的博客就几篇破文章,谁这么无聊要黑,我并没有立刻去网上找解决的 ...

  8. 中介者模式(Mediator)

    GOF:用一个中介对象来封装一系列的对象交互.中介者使对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互. 类图:

  9. ARM的一些基本概念

    MPU介绍: mpu是一个芯片,重力加速器(加速度)和陀螺仪(角速度) iic总线.在板上有iic控制器 连接着 最多128个外设,每个外设有地址,可以通信. 寄存器: cpu中的寄存器是为了加快运算 ...

  10. SVN 分支管理

    平时在工作中使用 SVN 只是限于 commit,update 这样的操作,至多再 reslove 解决一下冲突,没有用过分支管理.开发过程中一般都是一个功能开发完成之后整体进行提交,而最近在项目中有 ...