[Effective JavaScript 笔记]第61条:不要阻塞I/O事件队列

js程序是构建在事件之上的。输入可能来自不同的外部源。在一些语言中,我们习惯地编写代码来等待某个特定的输入。

var text=downloadSync(‘http://example.com/file.txt‘);
console.log(text);

像这样的形式downloadSync称为同步函数(或阻塞函数)。程序会停止做任何工作,而等待它的输入。在这个例子中,也就是等待从网络上下载文件的结果。由于在等待下载完成的期间,计算机可以做其他有用的工作,因此这样的语言通常为程序员提供一种方法来创建多个线程,即并行执行子计算。它允许程序的一部分停下来等待(阻塞)一个低速的输入,而程序的另一部分可以继续进行独立的工作。
在js中,大多的I/O操作都提供了异步的或非阻塞的API。程序员提供一个回调函数,一旦输入完成就可以被系统调用,而不是程序阻塞在等待结果的线程上。

var text=downloadSync(‘http://example.com/file.txt‘,function(text){
  console.log(text);
});

该API初始化下载进行,然后在内部注册表中存储了回调函数后立即返回,而不是被网络请求阻塞。当下载完成之后,系统会将下载完的文件的文本作为参数调用该已注册的回调函数。
随着事件的发生,事件被添加到应用程序的事件队列的末尾。js系统使用一个内部循环机制来执行应用程序。该循环机制每次都拉取队列底部的事件,以接收到这些事件的顺序来调用这些已经注册的js事件处理程序,并将事件的数据作为该事件处理程序的参数。
运行到完成机制担保的好处是当代码运行时,你完全掌握应用程序的状态。根本不必担心一些变量和对象属性的改变由于并发执行代码而超出你的控制。并发编程在js中往往比使用线程和锁的c++,java或c#更容易。
然而,运行到完成机制的不足是,实际上所有你编写的代码支撑着余下应用程序的继续执行。像浏览器这样的交互式应用程序中,一个阻塞的事件处理程序会阻塞任何将被处理的其他用户输入,甚至可能阻塞一个页面的渲染,从而导致页面失去响应的用户体验。在服务器环境中,一个阻塞的事件处理程序可能会阻塞将被处理的其他网络请求,从而导致服务器失去响应。
js并发的一个最重要的规则是绝不要在应用程序事件队列中使用阻塞I/O的API。在浏览器中,甚至几乎没有任何阻塞API是可用的,尽管有一些平台实现了。提供类似于downloadAsync功能的网络I/O的XMLHttpRequest库有一个同步的版本实现,被认为是不好的。对于web应用程序的交互性,同步的I/O会导致灾难性的后果,它在I/O操作完成之前一直会阻塞用户与页面的交互。
相比之下,异步的API用在基于事件的环境中是安全的,它们迫使应用程序逻辑在一个独立的事件循环“轮询”中继续处理。如上面的例子,假设需要几秒钟来下载网络资源,在这段时间里,数量庞大的其他事件很可能发生。在同步的实现中,这些事件就会堆积在事件队列中,而事件循环将停留等待该JS代码执行完成,这将阻塞任何其他事件的处理。在异步的版本中,JS代码注册一个事件处理程序并立即返回,这将在下载完成之前,允许其他处理程序处理这期间的事件。
在主应用程序事件队列不受影响的环境中,阻塞操作很少出问题。例如,web平台提供了Worker的API,该API使得产生大量的并行计算成为可能。不同于传统的线程执行,Workers在一个完全隔离的状态下执行,没有获取全局作用域或应用程序主线程web页面内容的能力。因此,它们不会妨碍主事件队列中运行的代码的执行。在一个Worker中,使用XMLHttpRequest同步的变种很少出问题。下载操作的确会阻塞Worker继续执行,但这并不会阻止页面的渲染或事件队列中的事件响应。在服务器端环境中,阻塞的API在启动一开始是没有问题的,也就是在服务器开始响应输入的请求之前。然后在处理请求期间,浏览器事件队列中存在阻塞的API就是有问题的啦。

提示

  • 异步API使用回调函数来延缓处理代价高昂的操作以避免阻塞主应用程序

  • js并发地接收事件,但会使用一个事件队列按序地处理事件处理程序
  • 在应用程序事件队列中绝不要使用阻塞的I/O
时间: 2024-10-15 14:50:38

[Effective JavaScript 笔记]第61条:不要阻塞I/O事件队列的相关文章

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

js函数有一个非凡的特性,即将其源代码重现为字符串的能力. (function(x){ return x+1 }).toString();//"function (x){ return x+1}" 反射获取函数源代码的功能很强大,使用函数对象的toString方法有严重的局限性.toString方法的局限性ECMAScript标准对函数对象的toString方法的返回结果(即该字符串)并没有任何要求.这意味着不同的js引擎将产生不同的字符串,甚至产生的字符串与该函数并不相关. 如果函数

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

函数是一种将代码作为数据结构存储的便利方式,代码之后可以被执行.这使得富有表现力的高阶函数抽象如map和forEach成为可能.它也是js异步I/O方法的核心.与此同时,也可以将代码表示为字符串的形式传递给eval函数以达到同样的功能.程序员面临一个选择:应该将代码表示为函数还是字符串?毫无疑问,应该将代码表示为函数.字符串表示代码不够灵活的一个重要原因是:它们不是闭包. 闭包回顾 看下面这个图 js的函数值包含了比调用它们时执行所需要的代码还要多的信息.而且js函数值还在内部存储它们可能会引用

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

js有5种原始值类型:布尔值.数字.字符串.null和undefined. 用typeof检测一下: typeof true; //"boolean" typeof 2; //"number" typeof "s";//"string" typeof null;//"object":ECMAScript把null描述为独特的类型,但返回值却是对象类型,有点困惑. 可以使用Object.prototype.t

[Effective JavaScript 笔记]第62条:在异步序列中使用嵌套或命名的回调函数

异步程序的操作顺序 61条讲述了异步API如何执行潜在的代价高昂的I/O操作,而不阻塞应用程序继续处理其他输入.理解异步程序的操作顺序刚开始有点混乱.例如,下面的代码会在打印"finished"之前打印"starting",即使这两个动作的程序源文件中以相反的顺序呈现. downloadAsync('file.txt',function(file){ console.log('finished'); }); console.log('starting'); down

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

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

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

构建异步API的一种流行的替代方式是使用promise(有时也被称为deferred或future)模式.已经在本章讨论过的异步API使用回调函数作为参数. downloadAsync('file.txt',function(file){ console.log('file:'+file); }); 基于promise的API不接收回调函数作为参数.相反,它返回一个promise对象,该对象通过其自身的then方法接收回调函数. var p=downloadP('file.txt'); p.th

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

对象属性无序性 js对象是一个无序属性集合. var obj={}; obj.a=10; obj.b=30; 属性a和属性b并没有谁前谁后之说.for...in循环,先输出哪个属性都有可能.获取和设置不同的属性与顺序无关,都会以大致相同的效率产生相同的结果.也就是说访问属性a和访问属性b,没有哪个访问更快之说.ES标准并未规定属性存储的任何特定顺序,甚至于枚举对象也未涉及.for...in循环会挑选一定的顺序来枚举对象的属性,标准允许js引擎自由选择一个顺序,它们的选择会微妙地改变程序行为.如要

[Effective JavaScript 笔记]第15条:当心局部块函数声明笨拙的作用域

嵌套函数声明.没有标准的方法在局部块里声明函数,但可以在另一个函数的顶部嵌套函数声明. function f(){return "global"} function test(x){ var result=[]; function f(){return "local";}//block-local if(x){ result.push(f()); } result.push(f()); return result; } test(true);//["loc

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

之前的43条,44条讨论了属性的枚举,但都没有彻底地解决属性查找中原型污染的问题.看下面关于字典的一些操作 'zhangsan' in dict; dict.zhangsan; dict.zhangsan=22; js的对象操作总是经继承的方式工作的.即使是一个空的对象字面量也是继承了Object.protoype属性. var dict={}; 'zhangsan' in dict;//false 'lisi' in dict;//false 'wangwu' in dict;//false'