node.js events

Javascript is an event driven language and this is the most important thing that makes node.js an awesome toolkits. Javascript works like human. For example if there are 5 things to do, they are

1. Make cup noodles( takes 3 mins )
2. Answer a phone call( takes 1 min )
3. Go to toilet( takes 20 secs )
4. Eat the noodles( takes 5 mins )
5. Throw the cup to trash can( takes 3 secs )

A normal person will first make noodles and while waiting the water to boil he can answer a phone call and go to toilet. When it’s ready he then eat the noodles and throw the cup to trash can. However with non-event driven language for example PHP is not that easy to achieve that.

// Start making cup noodles
make_cup_noodles();

// While waiting for the noodles to be ready the phone rings,
// but I have to wait until the noodles to be cooked so I missed the phone call.
answer_a_phone_call();

// At the same time I feel like I need to pee but I still have to wait for the noodles.
// So I wet my pants...
go_to_toilet();

// Then I eat the noodles with my wet pants
eat_the_noodles();

// and throw the cup to trash can with my wet pants
throw_the_cup_to_trash_can();

With event driven language like javascript things would be different. However the above code still does not work right.

steps.js | Contents all the steps we are going to do
module.exports = {

  cup_noodles : 'This is the not yet cooked cup noodles',

  make_cup_noodles : function( callback ){
    var self = this;

    console.log( 'Start making cup noodles' );

    // simulate a time consuming function
    setTimeout( function(){
      self.cup_noodles = self.cup_noodles === 'This is the not yet cooked cup noodles' ?
        'Cup noodles are ready' : self.cup_noodles;

      console.log( self.cup_noodles );

      callback && callback.call( this );
    }, 3000 );
  },

  answer_a_phone_call : function(){
    var action = this.ringing === 'Ringing...' ?
        'I answered the phone call' : 'I missed the phone call';

      console.log( action );
  },

  go_to_toilet : function(){
    this.pants = 'Off';
  },

  eat_the_noodles : function( callback ){
    var self = this;
    
    setTimeout( function(){
      self.cup_noodles = self.cup_noodles === 'Cup noodles are ready' ?
        'I finished eating' : 'I ate nothing...';
      
      console.log( self.cup_noodles );
      
      callback && callback.call( this );
    }, 5000 );
  },

  throw_the_cup_to_trash_can : function(){
    var self = this;
    
    setTimeout( function(){
      self.cup_noodles = self.cup_noodles === 'I finished eating' ?
        'The cup noodles are finished and are in the trash can now' :
        'The cup noodles are wasted';

      console.log( self.cup_noodles );
    }, 30 );
  }
};
pee.js | Pee action inside
module.exports = {

  action : '',

  pants : 'On',

  explode : function( callback ){
    var self = this;

    callback && callback.call( this );

    this.action = this.pants === 'On' ?
      'Peeing on my pants' : 'Releasing...';

    console.log( this.action );

    setTimeout( function(){
      self.pants = self.action === 'Peeing on my pants' ?
        'I wet my pants' : 'I finished peeing';

      console.log( self.pants );
    }, 500 );
  }
};
phone.js | Phone ring action
module.exports = {

  ringing : '',

  ring : function( callback ){
    var self = this;
    
    this.ringing = 'Ringing...';
    console.log( this.ringing );
    
    callback && callback.call( this );
    
    setTimeout( function(){
      self.ringing = 'Ringing stopped';
      console.log( self.ringing );
    }, 1000 );
  }
};
wrong.js | Demonstrate the wrong operation
var steps = require( './steps' ),
    phone = require( './phone' ),
    pee = require( './pee' );

// Start making cup noodles
steps.make_cup_noodles();

// Yes I am now event driven so I don't have to wait for the noodles to be ready
// But we have a new problem here,
// with the same scope of functions they are triggered nearly at the same time.
phone.ring();

// Which means I might not be able to answer the phone call
steps.answer_a_phone_call();

// Feel like going to the toilet
pee.explode();

// Same thing happens here
steps.go_to_toilet();

// I want to eat the noodles but it's not ready yet
steps.eat_the_noodles();

// The same thing happens here, I throw the cup noodles that is still being cooked.
// So in the end I still wet my paint and did not answer the phone call
// plus I still can't have my cup noodles
steps.throw_the_cup_to_trash_can();
Result
Start making cup noodles
Ringing...
I missed the phone call
Peeing on my pants
The cup noodles are wasted
I wet my pants
Ringing stopped
The cup noodles are wasted
I ate nothing...

To make sure one thing happens after another, call those functions in a callback.

right.js | Demonstrate the right way
var steps = require( './steps' ),
    phone = require( './phone' ),
    pee = require( './pee' );

steps.make_cup_noodles( function(){
  steps.eat_the_noodles( function(){
    steps.throw_the_cup_to_trash_can();
  });
});

phone.ring( steps.answer_a_phone_call );

pee.explode( steps.go_to_toilet );
Result
Start making cup noodles
Ringing...
I answered the phone call
Releasing...
I finished peeing
Ringing stopped
Cup noodles are ready
I finished eating
The cup noodles are finished and are in the trash can now
Hope the above example shows you how a event driven language works and why node.js is so fast

node.js Events

If you look at the node.js doc, there is an events section. I’m not going to go through each api of node.js events module here. Instead I’ll explain why and when to use this module.

From the previous example we know if we want to make sure one thing happens after another, we have to write it in the callback. But how if we have multiple and complex actions? We would end up with nested callbacks like the following

do_a( function(){
  do_b( function(){
    do_c( function(){
      do_d( function(){
        ...
      });
    });
  });
});

It looks ugly and is tighten up. It’s not only hard to split the code into modules but also hard to extend the functionality. This is how node.js events comes in handy. With EventEmitter we can change the above code to the following

event.js
var event = require( 'events' ).EventEmitter;

module.exports = new event;
do_a.js
var event = require( './event' );

module.exports = function(){
  console.log( 'we are going to call do_a' );
  event.emit( 'do_a' );
};
do_b.js
var event = require( './event' );

module.exports = function(){
  event.on( 'do_a', function(){
    console.log( 'we are going to call do_b' );
    event.emit( 'do_b' );
  });
};
do_c.js
var event = require( './event' );

module.exports = function(){
  event.on( 'do_b', function(){
    console.log( 'we are going to call do_c' );
    event.emit( 'do_c' );
  });
};
do_d.js
var event = require( './event' );

module.exports = function(){
  event.on( 'do_c', function(){
    console.log( 'we are going to call do_d' );
    event.emit( 'do_d' );
  });
};
run.js
var event, dos;

event = require( './event' );
todos = [ './do_d', './do_c', './do_b', './do_a' ];

event.on( 'do_a', function(){
  console.log( 'i can do something to do_a out side of do_a' );
});

event.on( 'do_b', function(){
  console.log( 'i can do something to do_a out side of do_b' );
});

event.on( 'do_c', function(){
  console.log( 'i can do something to do_a out side of do_c' );
});

event.on( 'do_d', function(){
  console.log( 'i can do something to do_a out side of do_d' );
});

todos.forEach( function( name ){
  require( name )();
});
Result of calling run.js
we are going to call do_a
i can do something to do_a out side of do_a
we are going to call do_b
i can do something to do_a out side of do_b
we are going to call do_c
i can do something to do_a out side of do_c
we are going to call do_d
i can do something to do_a out side of do_d

From the above result it might appear to you that it seems to be more complicated. Yes, for a small project it is. But for a larger project we can split our code into other files and still keep the ordering. It is a must for building flexible applications


Related posts