深入理解node.js异步编程

  1. 概述目前开源社区最火热的技术当属Node.js莫属了,作为使用Javascript为主要开发语言的服务器端编程技术和平台,一开始就注定会引人瞩目。 当然能够吸引众人的目光,肯定不是三教九流之辈,必然拥有独特的优势和魅力,才能引起群猿追逐。其中当属异步IO和事件编程模型,本文据Node.js的异步IO和事件编程做深入分析。 ##2. 什么是异步同步和异步是一个比较早的概念,大抵在操作系统发明时应该就出现了。举一个最简单的生活中的例子,比如发短信的情况会比较好说明他们的区别:同步:正在处于苦逼工作状态中的我,但狗屎运的交到了女朋友并正处于处于热恋期,因此发送短信给她询问那个餐厅吃饭,急不可耐的看着手机等待短信回复,收到信息看完是否加班或者下班;异步:正处于公司运营决策关键工作状态中的你,不可以被打断太久,随便发送了一条询问老婆什么时候做好晚饭然后吃饭的短信后立马返回工作,一边工作一边等待短信回复通知,根据通知决定是否再工作和下班。由此可以看出,同步和异步的特点是:

  至少在两个对象之间需要协作(男朋友和女朋友,老公和老婆);

  两个对象都需要处理一系列的事情(工作和吃饭)。 另一个类似的关于CPU计算和磁盘操作编的例子: 同步:CPU需要计算10个数据,每计算一个结果后,将其写入磁盘,等待写入成功后,再计算下一个数据,直到完成。 异步:CPU需要计算10个数据,每计算一个结果后,将其写入磁盘,不等待写入成功与否的结果,立刻返回继续计算下一个数据,计算过程中可以收到之前写入是否成功的通知,直到完成。

  ##3. 为什么需要异步知其然,还要知其所以然,读者可能会问,为什么存在异步?根据上面发短信和磁盘操作的例子,答案很明显,为了提高办事的效率,CPU计算速度和磁盘的读写速度差太远了,磁盘供不应求,因此有了计算机的存储系统的分层设计,平衡了效率和成本。可以说懒惰推动人类的进步,任何可以降低花费时间而达到同等功效的方法肯定会被优先采用。发送短信时等待对方回复的时间纯粹的浪费掉了,CPU写入磁盘等待返回的结果的等待时间也被无情的消耗了,这是一个讲究效率的时代完全不能忍受的,因此让员工一直处于忙碌状态,最大限度的榨取员工价值是老板追求的,让CPU和磁盘都不停的满负荷处理事务也是效率需要的。因此,异步处理出现了。

  ##4. Node.js异步IO与事件初次接触Node.js,恐怕任何人都会被先先灌输的第一条Node.js就与众不同的地方:异步IO和事件驱动。毫无疑问,这确实是Node.js最令人津津乐道的特色之处,也是本文重点分析的地方。 ###4.1 Node.js异步机制由于异步的高效性,node.js设计之初就考虑做为一个高效的web服务器,作者理所当然地使用了异步机制,并贯穿于整个node.js的编程模型中,新手在使用node.js编程时,往往会羁绊于由于其他编程语言的习惯,比如C/C++,觉得无所适从。我们可以从以下一段简单的睡眠程序代码窥视出他们的区别,下面是摘自《linux程序设计》打印10个时间的C代码:

  #include

  #include

  #include

  int main()

  {

  int i;

  time_t the_time;

  for(i = 1; i <= 10; i++) {

  the_time = time((time_t *)0);

  printf("The time is %ld\n", the_time);

  sleep(2);

  }

  exit(0);

  }

  编译后打印结果如下: The time is 1396492137 The time is 1396492139 The time is 1396492141 The time is 1396492143 The time is 1396492145 The time is 1396492147 The time is 1396492149 The time is 1396492151 The time is 1396492153 The time is 1396492155 从C语言的打印结果可以发现,是隔2秒打印一次,按照C程序该有的逻辑,代码逐行执行。以下Node.js代码本意如同上述C代码,使用目的隔2秒打印一次时间,共打印10条(初次从C/C++转来接触Node.js的程序员可能会写出下面的代码):

  function test() {

  for (var i = 0; i < 10; i++) {

  console.log(new Date);

  setTimeout(function(){}, 2000); //睡眠2秒,然后再进行一下次for循环打印

  }

  };

  test();

  打印结果: Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  Tue Apr 01 2014 14:53:22 GMT+0800 (中国标准时间)

  观察结果发现都是在14:53:22同一个时间点打印的,根本就没有睡眠2秒后再执行下一轮循环打印!这是为什么?从官方的文档我们看出setTimeout是第二个参数表示逝去时间之后在执行第一个参数表示的callback函数,因此我们可以分析, 由于Node.js的异步机制,setTimeout每个for循环到此之后,都注册了一个2秒后执行的回调函数然后立即返回马上执行console.log(new Date),导致了所有打印的时间都是同一个点,因此我们修改for循环的代码如下:

  for (var i = 0; i < 10; i++) {

  setTimeout(function(){

  console.log(new Date);

  }, 2000);

  }

  执行结果如下所示: Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:30:35 GMT+0800 (中国标准时间) 神奇,仍然是同一个时间点,见鬼!冷静下来分析,时刻考虑异步,for循环里每次setTimeout注册了2秒之后执行的一个打印时间的回调函数,然后立即返回,再执行setTimeout,如此反复直到for循环结束,因为执行速度太快,导致同一个时间点注册了10个2秒后执行的回调函数,因此导致了2秒后所有回调函数的立即执行。 我们在for循环之前添加console.log("before FOR: " + new Date)和之后console.log("after FOR: " + new Date),来验证我们的推测,打印结果如下(后面省略8条相同的打印行): before FOR: Thu Apr 03 2014 09:42:43 GMT+0800 (中国标准时间)

  after FOR: Thu Apr 03 2014 09:42:43 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:42:45 GMT+0800 (中国标准时间)

  Thu Apr 03 2014 09:42:45 GMT+0800 (中国标准时间) …… (省略与上一行8条相同的打印行)由此可以窥视出Node.js异步机制的端倪了,在for循环中的代码于其后的代码几乎在一个单位秒内完成,而定时器中的回调函数则按要求的2秒之后执行,也是同一秒内执行完毕。那么如何实现最初C语言每隔2秒打印一个系统时间的需求函数呢,我实现了如下一个wsleep函数,放在for循环中,可以达到该目的:

  function wsleep(milliSecond) {

  var startTime = new Date().getTime();

  while(new Date().getTime() <= milliSecond + startTime) {

  }

  }

  但是该函数有一个令他无法在项目中使用的缺陷,请问为什么? ###4.2 Node.js事件编程事件编程并不是一个新的概念,做过界面UI编程的程序猿们可以觉得事件再熟悉不过了,特别是客户端开发和web开发的感触颇深吧,如Android、ios、或是javascript前端编程的工程师们,一个按钮、一个列表项、一个长按操作等等,每次按下都会由操作系统或者浏览器产生一个事件,你需要做的工作就是编写和注册这个事件的回调函数(可能各自领域内不称为回调函数,但是从操作系统的角度考虑其实就是一个回调函数),当这个事件发生时,执行你的回调函数。Node.js与众不同的时,它基因里就是由事件和异步组成的。请看用于生产环境中的真实项目代码的一个片段(略去了一些不相关的代码),我加上一段关于事件信息的注释,让读者更清晰:

  self.sio.sockets.on(‘connection‘, function(socket) { //监听socket连接事件

  var addr = socket.handshake.address;

  var limiter = new RateLimiter(constant.RL_MAXREQRATELIMIT, constant.RL_RATELIMITUNIT, true);

  var connect = new Connection(socket);

  then(function(defer) {

  if (ipLimit) {

  throttle.throttleHandle(connect, null, defer); //结果回调处理事件

  } else {

  defer(null); //发送处理结果事件

  }

  }).all(function(defer) { //收到处理结果事件

  socket.on(‘message‘, function(data) { //监听数据传输事件

  cloudKeyMain(connect, 1, data, cloudKeyApi);

  });

  });

  socket.on("disconnect", function(data) { //监听socket离线事件

  var currentSockClient = connect.client;

  if (currentSockClient) {

  currentSockClient.signalOffline(); //发送客户端离线事件

  }

  });

  });

  从上面的代码,我们可以看出Node.js无所不在的事件机制,事件机制让我们专注与代码业务的处理流程,提高了软件开发的效率,降低了http://www.chinamaofa.com/代码之间的耦合,让人不被琐事缠绕,编程更有趣。如何开始一个简单的Node.js事件编程呢,答案是使用Node.js的javascript API核心模块events的events.EventEmitter类即可完成,下面以一个QQ的在线和离线来说明,事件机制的使用主要包括3个方面的内容:

  继承events.EventEmitter事件类,主要是屏蔽事件机制的实现(其实原理很简单),让我们直接使用;

  事件的注册;

  事件的发布。

  var events = require(‘events‘);

  var util = require(‘util‘);

  function MyQQ() {

  events.EventEmitter.call(this);

  //……

  }

  util.inherits(MyQQ, events.EventEmitter);

  OK,上述代码就完成了事件机制的添加,此时,我们的工作为QQ添加事件注册函数进行事件的注册,事件注册主要是使用EventEmitter的http://www.sansewa.com/zhifa_anli/toufa/2018-08-24/816.htmlon()完成,因为我们继承了EventEmitter,可以直接使用on函数,我们在on函数的第二个参数callback函数中自定义处理业务,并注册自己的上线事件,以下是一个QQ上线时简单的处理业务:

  function onlineHandle(QQNumber) {

  //获取和QQNumber的联系人列表

  //获取离线消息

  //……

  }

  var myQQ = new MyQQ();

  myQQ.on(“onLine”, onlineHandle);

  上述代码完成了事件的处理,下面轮到在什么时候发布这个事件,下述的一个业务场景中可能是需要发布该事件的,发布事件用emit()函数:

  function main() {

  //连接服务器

  //检测登录状态

  //登录服务器成功后发布事件

  myQQ.emit(“onLine”,123655245);

  }

  上述myQQ.emit()函数执行后发布了onLine事件后,会立即执行onlineHandle()函数,处理我们注册的业务逻辑,需要注意的是,事件发布函数emit第二个参数后的参数个数需要和我们注册时的处理函数参数个数相同并且顺序一致才能正确处理,为什么有这样的要求?这需要从Node.js事件的原理说起。

原文地址:https://www.cnblogs.com/zhang-864200/p/9529945.html

时间: 2024-11-07 20:59:33

深入理解node.js异步编程的相关文章

理解Node.js事件驱动编程

Node.js现在非常活跃,相关生态社区已经超过Lua(基本上比较知名的功能都有nodejs模块实现). 但是我们为何要使用Node.Js?相比传统的webserver服务模式,nodejs有什么优点优势? Node.Js是基于javascript语言,建构在google V8 engine以及Linux上的一个非阻塞事件驱动IO框架.nodejs是单进程单线程,但是基于V8的强大驱动力,以及事件驱动模型,nodejs的 性能非常高,而且想达到多核或者多进程也不是很难(现在已经有大量的第三方mo

一个例子读懂 JS 异步编程: Callback / Promise / Generator / Async

JS异步编程实践理解 回顾JS异步编程方法的发展,主要有以下几种方式: Callback Promise Generator Async 需求 显示购物车商品列表的页面,用户可以勾选想要删除商品(单选或多选),点击确认删除按钮后,将已勾选的商品清除购物车,页面显示剩余商品. 为了便于本文内容阐述,假设后端没有提供一个批量删除商品的接口,所以对用户选择的商品列表,需要逐个调用删除接口. 用一个定时器代表一次接口请求.那思路就是遍历存放用户已选择商品的id数组,逐个发起删除请求del,待全部删除完成

初步理解require.js模块化编程

初步理解require.js模块化编程 一.Javascript模块化编程 目前,通行的Javascript模块规范共有两种:CommonJS和AMD. 1.commonjs 2009年,美国程序员Ryan Dahl创造了node.js项目,将javascript语言用于服务器端编程,这标志”Javascript模块化编程”正式诞生. 在浏览器环境下,没有模块也不是特别大的问题,毕竟网页程序的复杂性有限:但是在服务器端,一定要有模块,与操作系统和其他应用程序互动,否则根本没法编程. node.j

Node 初探异步编程

从C/C++转过来最开始不适应的就是这个了吧.=-= Node是单线程,那么怎么提高效率?怎么解决一些阻塞问题?Node的基因里使用了异步IO,上次在http://www.cnblogs.com/zhangmingzhao/p/7564738.html 已经说到这个问题,Node的异步机制往往伴随着回调. 先看一个关于CPU的例子来比较同步和异步:  同步:CPU需要计算10个数据,每计算一个结果后,将其写入磁盘,等待写入成功后,再计算下一个数据,直到完成. 异步:CPU需要计算10个数据,每计

深入解析js异步编程利器Generator

我们在编写Nodejs程序时,经常会用到回调函数,在一个操作执行完成之后对返回的数据进行处理,我简单的理解它为异步编程. 如果操作很多,那么回调的嵌套就会必不可少,那么如果操作非常多,那么回调的嵌套就会变得让人无法忍受了. 我们知道的Promises就是问了解决这个问题而提出来的.然而,promises并不是一种新的功能,它只是一种新的写法,原来横向发展的回调函数,被排成了队竖向发展. 然而,Generator不同,它是一种新的解决方案. 文章中提到的所有代码都可以在这里找到源码:[查看源码].

理解Node.js的事件轮询

前言 总括 : 原文地址:理解Node.js的事件轮询 Node小应用:Node-sample 智者阅读群书.亦阅历人生 正文 Node.js的两个基本概念 Node.js的第一个基本概念就是I/O操作开销是巨大的: 所以,当前变成技术中最大的浪费来自于等待I/O操作的完毕.有几种方法能够解决性能的影响: 同步方式:按次序一个一个的处理请求.利:简单.弊:不论什么一个请求都能够堵塞其它全部请求. 开启新进程:每一个请求都开启一个新进程.利:简单:弊:大量的链接意味着大量的进程. 开启新线程:每一

JS 异步编程六种方案

前言我们知道Javascript语言的执行环境是"单线程".也就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务. 这种模式虽然实现起来比较简单,执行环境相对单纯,但是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行.常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方,其他任务无法执行. 为了解决这个问题,Javascript语言将任务的执行模式分成两种

方便大家学习的Node.js教程(一):理解Node.js

理解Node.js 为了理解Node.js是如何工作的,首先你需要理解一些使得Javascript适用于服务器端开发的关键特性.Javascript是一门简单而又灵活的语言,这种灵活性让它能够经受住时间的考验.函数.闭包等特性使Javascript成为一门适合Web开发的理想语言. 有一种偏见认为Javascript是不可靠的,然而事实并非如此.人们对Javascript的偏见来源于DOM,DOM是浏览器厂商提供的用于Javascript与浏览器交互的API,不同浏览器厂商实现的DOM存在差异.

Javascript教程:js异步编程的4种方法详述(转载)

文章收集转载于(阮一峰的网络日志) 你可能知道,Javascript语言的执行环境是“单线程”(single thread). 所谓“单线程”,就是指一次只能完成一件任务.如果有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推. 这种模式的好处是实现起来比较简单,执行环境相对单纯:坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行.常见的浏览器无响应(假死),往往就是因为某一段Javascript代码长时间运行(比如死循环),导致整个页面卡在这个地方