Node Events 3 – Advanced Patterns

Node in Practice 第四章 – EventsEmitter – Advanced Patterns。

书中展示了 EventEmitter 的一些高级用法,有利于提高 Node 代码编写效率。

Reflection

// Node in practice 4.3 Reflection

// 1. Keeping tabs on new listeners
;(function () {
  'use strict';

  const util = require('util');
  const events = require('events');

  ;const EventTracker = (function () {
    
    const self = { };
    const $ = function () {
      events.EventEmitter.call(this);
    };

    util.inherits($, events.EventEmitter);

    return $;
  }());

  // The demo usage:
  const eventTracker = new EventTracker();

  eventTracker.on('newListener', function (name, listener) {
    console.log(`Event name added: ${name}`);
  });

  eventTracker.on('a listener', function () {
    // This will cause 'newListenner' to fire
  });
}());

// 2. Automatically triggering events based on new listener
;(function () {
  'use strict';

  const util = require('util');
  const events = require('events');

  ;const Pulsar = (function () {
    
    const self = { };

    const $ = function (speed, times) {
      events.EventEmitter.call(this);

      self.speed = speed;
      self.times = times;

      self._start = __start.bind(this);
      self._stop = __stop.bind(this);

      this.on('newListener', function (eventName, listener) {
        eventName === 'pulse' && self._start();
      });
    };

    util.inherits($, events.EventEmitter);

    $.prototype.stop = function () {
      self._stop();
    };

    function __start () {
      const id = setInterval((function () {
        this.emit('pulse');
        self.times--;
        self.times === 0 && clearInterval(id);
      }).bind(this), self.speed);
    }

    // Quering listeners
    function __stop () {
      if (this.listeners('pulse').length === 0) {
        throw new Error('No listeners have been added!');
      }
    }

    return $;
  }());

  // The demo usage:
  const pulsar = new Pulsar(500, 5);

  // pulsar.on('pulse', function () {
  //   console.log('.');
  // });
  pulsar.stop();
}());

newListener 是 EventEmitter 一个特殊事件,用于监听新事件的创建。

Library

// Node in practice Technique 24 - Detecting and exploiting EventEmitter

// 4.12 Reusing EventEmitter in Express
;(function () {
  'use strict';

  const express = require('express');
  const app = express();
  
  app.on('hello-alert', () => {
    console.warn('Waning!');
  });

  app.get('/', (req, res) => {
    res.app.emit('hello-alert');
    res.send('hello world');
  });

  app.listen(process.env.PORT || 3000);
}());

// 4.13 Rusing EventEmitter in the redis module
;(function () {
  'use strict';

  const redis = require('redis');
  const client = redis.createClient();
  
  client.on('error', err => {
    console.error(`Error: ${err}`);
  });

  client.on('monitor', (timestame, args) => {
    console.log(`Time: ${timestame}, arguments: ${args}`);
  });

  client.on('ready', () => {
    // start app here
  });
}());

很多 Node 开源模块都是基于 EventEmitter 的,诸如 express、redis。通常他们都混入了 EventEmitter,充分利用这一点,可以在一个 app 的不同模块之间进行高效事件通信。

Categorizing

// Node in practice Technique 25 - Categorizing event
// 4.13 Categorizing event names using an object

;(function () {
  'use strict';

  ;const MusicPlayer = (function () {

    const util = require('util');
    const events = require('events');

    const self = { };

    const $ = function (track) {
      events.EventEmitter.call(this);

      this.on($.events.play, this.play.bind(this));
    };

    util.inherits($, events.EventEmitter);

    $.prototype.play = function () {
      self.playing = true;
    };

    return $;
  }());

  const e = MusicPlayer.events = {
    play: 'play',
    pause: 'pause',
    stop: 'stop',
    ff: 'ff',
    rw: 'rw',
    addTrack: 'add-track'
  };

  const musicPlayer = new MusicPlayer();

  musicPlayer.on(e.play, () => {
    console.log('Now playing');
  });
  musicPlayer.emit(e.play);
}());

为事件建立一个字典,可以提高代码可读性和可调试性。

Third-party

// Node in practice Technique 24 - Third-party modules & extensions

// 4.15 Using RabbitMQ with Node
; (function () {
  'use strict';

  const rabbitHub = require('rabbitmq-nodejs-client');
  const subHub = rabbitHub.create({ task: 'sub', channel: 'myChannel' });
  const pubHub = rabbitHub.create({ task: 'pub', channel: 'myChannel' });

  subHub.on('connection', function (hub) {
    hub.on('message', function (msg) {
      console.log(msg);
    }.bind(this));
  });
  subHub.connect();

  pubHub.on('connection', function (hub) {
    hub.send('Hello World!');
  });
  pubHub.connect();
}());

// 4.16 Using ØMQ with Node
; (function () {
  'use strict';

  const zmq = require('zmq');
  const push = zmq.socket('push');
  const pull = zmq.socket('pull');

  push.bindSync('tcp://127.0.0.1:3000');
  pull.connect('tcp://127.0.0.1:3000');

  console.log('Producer bound to port 3000');

  setInterval(function () {
    console.log('sending work');
    push.send('some work');
  }, 500);

  pull.on('message', function (msg) {
    console.log('work: %s', msg.toString());
  });
}());

// 4.17 Using Redis Pub/Sub with Node
; (function () {
  'use strict';

  const redis = require('redis');
  const client1 = redis.createClient();
  const client2 = redis.createClient();

  let msg_count = 0;
  client1.on('subscribe', function (channel, count) {
    client2.publish('channel', 'Hello world.');
  });

  client1.on('message', function (channel, message) {
    console.log(`client1 channel ${channel}: ${message}`);
    client1.unsubscribe();
    client1.end();
    client2.end();
  });

  client1.subscribe('channel');
}());

// 4.18 Using Redis Pub/Sub with Node
; (function () {
  'use strict';

  const signals = require('signals');
  const myObject = {
    started: new signals.Signal()
  };
  function onStarted(param1, param2) {
    console.log(param1, param2);
  }
  myObject.started.add((onStarted));
  myObject.started.dispatch('hello', 'world');
}());

书中介绍了一些可以替代 EventEmitter 的模块,可以用于诸如分布式集群等其他场合,有几个例子跑不了。

参考:

  1. Alex Young, Marc Harter. Node in Practice. 2015

作者: YanWen

Web 开发者

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google photo

You are commenting using your Google account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s