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