node.js + MongoDB + AngularJS - 4 事件、监听器、定时器、回调

来源:互联网 发布:mac display color 编辑:程序博客网 时间:2024/04/30 00:22

node.js通过其强大的事件驱动模型提供了可扩展性和性能。了解事件模型至关重要,因为它可能迫使你改变设计应用程序的思维。

1. 了解Node.js事件模型

Node.js应用程序在一个单线程的事件驱动模型中运行。

1.1 比较事件回调和线程模型

  • 传统的线程网络模型

请求进入一个web服务器,并被分配给一个可用的线程。对于该请求的处理工作继续在该线程上进行,直到请求完成并发出响应。

  • Node.js事件模型的工作原理

Node.js不是在各个线程为每个请求执行所有的工作,反之,它把工作添加到一个事件队列中,然后有一个单独的线程运行一个事件循环把这个工作提取出来。

1.2 在Node.js中阻塞I/0

阻塞I/O的例子:

  • 读取文件
  • 查询数据库
  • 请求套接字
  • 访问远程服务

Node.js使用事件回调来避免对阻塞I/O的等待。因此,执行阻塞I/O的任何请求都在后台的不同的线程中执行。Node.js在后台实现线程池。当该块的I/O从时间队列中检测一个事件,Node.js从线程池中获取一个线程,并在那里执行功能,而不是主事件循环线程执行功能。这可以防止阻塞I/O阻碍事件队列中的其余事件。

2. 将工作添加到事件队列

在Node.js应用程序中,你可以使用下列方法之一传递回调函数来在事件队列中调度工作:

  • 对阻塞I/O库调用之一做出调用
  • 对内置的事件
  • 创建自己的事件发射器并对它们添加自定义的监听器
  • 使用process.nextTick选项来调度在事件循环的下一次循环中被提取出的工作
  • 使用定时器来调度在特定时间数量或每隔一段时间后要做的工作

2.1 实现定时器

  • 用超时时间来延迟工作

超时定时器用于将工作延迟一个特定时间数量。当时间到了,回调函数执行,而定时器消失。对于只需要执行一次的工作,你应该使用超时时间。

setTimeout( callback, delayMilliSeconds, [ args ] ) ;setTimeout( myFunc, 1000 ) ;

setTimeout() 函数返回一个定时器对象的ID,你可以在delayMilliSeconds到期前的任何时刻把此ID传递给clearTimeout( timeoutID )来取消超时时间函数。

myTimeout = setTimeout( myFunc, 1000000 ) ;clearTimeout( myTimeout ) ;
  • 用时间间隔执行定期工作

时间间隔定时器用于按定期的延迟时间间隔执行工作。当延迟时间结束时,回调函数被执行,然后再次重新调度为该延迟时间。对于必须定期进行的工作,你可以使用时间间隔。

myInterval = setInterval( myFunc, 1000 ) ;clearInterval( myInterval ) ;
  • 使用即时计时器立即执行工作

即时计时器用来在I/O时间的回调函数开始执行后,但任何超时时间或时间间隔事件被执行之前,立刻执行工作。它们允许你把工作调度为在事件队列中的当前事件完成之后执行。你应该使用即时定时器为其他回调产生长期运行的执行段,以方式I/O饥饿。

myImmediate = setImmediate( myFunc ) ;clearImmediate( myImmediate ) ;
  • 从事件循环中取消定时器引用

当定时器事件回调是留在事件队列中的仅有事件时,通常你不会希望它们继续被调度。Node.js提供了一个非常有用的工具来处理这种情况。这个工具是在setInterval和setTimeout返回的对象中可用的unref()函数,它让你能够在这些事情是队列中仅有的事件时,通知事件循环不要继续。

myInterval = setInterval( myFunc ) ;myInterval.unref() ;

可以重新引用:

myInterval.ref() ;

2.2 使用nextTick来调度工作

在事件队列上调度工作的一个非常有用的方法是使用process.nextTick( callback )函数。此函数调度要在事件循环的下一次循环中运行的工作。不想setImmediate()方法,nextTick()在I/O事件被触发之前执行。这可能会导致I/O事件的饥饿,所以Node.js通过默认值1000的process.maxTickDepth来限制事件队列的每次循环可执行的nextTick()事件的数目。

2.3 实现事件发射器和监听器

创建自己的自定义事件,以及实现当一个事件被发出时执行的监听器回调。

  • 将自定义事件添加到Javascript对象

事件使用一个EventEmitter对象发出。这个对象包含在events模块中。emit( eventName, [ args ] ) 函数触发eventName事件,包含所提供的任何参数。

var events = require( 'events' ) ;var emitter = new events.EventEmitter() ;emitter.emit( 'simpleEvent' ) ;

直接把事件添加到你的Javascript对象,需要通过在对象实例中调用events.EventEmitter.call( this ) 来继承EventEmitter功能。你还需要把events.EventEmitter.prototype添加到对象的原型中。

function MyObj() {    Events.EventEmitter.call( this ) ;}MyObj.prototype.__proto__ = events.EventEmitter.prototype ;var myObj = new MyObj() ;myObj.emit( 'someEvent' ) ;
  • 把事件监听器添加到对象

  • .addListener( eventName, callback ) : 将回调函数附加到对象的监听器中。每当eventName事件被触发时,回调函数就被重置在事件队列中执行。

  • .on( eventName, callback ) : 同.addListener()
  • .once( eventName, callback ) : 只有eventName事件第一次被触发时,回调函数才被放置在事件队列中执行。

  • 在对象中删除监听器

  • .listeners( eventName ) : 返回一个连接到eventName事件的监听器函数的数组

  • .setMaxListerers( n ): 如果多余n的监听器都加入到EventEmitter对象,就触发警报。默认值是10
  • .removeListener( eventName, callback ) : 将callback函数从EventEmitter对象的eventName事件中删除

3. 实现回调

  • 将参数传递给回调函数
  • 在循环内处理回调函数参数
  • 以及嵌套回调

3.1 向回调函数传递额外的参数

大部分回调函数都有传递给它们的自动参数,如错误或结果缓冲区。使用回调时,常见的一个问题是如何从调用函数给他们传递额外的参数。做到这一点的方法是在一个匿名函数中实现该参数,然后用来自匿名函数的参数调用回调函数。

var events = require( 'events' ) ;function CarShow() {  events.EventEmitter.call( this ) ;  this.seeCar = function( make ) {    this.emit( 'sawCar', make ) ;  }}CarShow.prototype.__proto__ = events.EventEmitter.prototype ;var show = new CarShow() ;function logCar( make ) {  console.log( 'Saw a ' + make ) ;}function logColorCar( make, color ) {  console.log( 'Saw a %s %s', color, make ) ;}show.on( 'sawCar', logCar ) ;show.on( 'sawCar', function( make ) {  var colors = [ 'red', 'blue', 'black' ] ;  var color = colors[ Math.floor( Math.random() * 3 ) ] ;  logColorCar( make, color ) ;} ) ;show.seeCar( 'Ferrari' ) ;show.seeCar( 'Porsche' ) ;show.seeCar( 'Bugatti' ) ;show.seeCar( 'Lamborghini' ) ;show.seeCar( 'Astom Martin' ) ;

3.2 在回调中实现闭包

  • 闭包:变量被绑定到一个函数的作用域,但不绑定到他的父函数的作用域。

当你执行一个异步回掉时,父函数的作用域可能改变,如果某个回调函数需要访问父函数的作用域的变量,就需要提供闭包,使这些值在回调函数从事件队列被提取出时,可以得到。

function logCar( logMsg, callback ) {  process.nextTick( function() {    callback( logMsg ) ;  } ) ;}var cars = [ 'Ferrari', 'Porsche', 'Bugatti' ] ;for( var idx in cars ) {  var message = 'Saw a ' + cars[ idx ] ;  logCar( message, function() {    console.log( 'Normal Callback: ' + message ) ;  } ) ;}for( var idx in cars ) {  var message = 'Saw a ' + cars[ idx ] ;  ( function( msg ){    logCar( msg, function() {      console.log( 'Closure Callback: ' + msg ) ;    } );  } )( message ) ;}

3.3 链式回调

使用异步函数时,如果两个函数都在事件队列上,则你无法保证它们的运行顺序。解决这一问题的最佳方式是让来自异步函数的回调再次调用该函数,知道没有更多的工作要做,以执行链式回调。

0 0
原创粉丝点击