This post is more like a drama script. I would love to see the below conversation as a one-act play on a stage!
Master : Can you write a function to read a contents of a file?
Apprentice: hmm.. that's very easy!
1
2
3
function read ( filename ){
return fs . readFileSync ( filename , 'utf8' );
}
Master: Okies....say the file is like 10GB in size?
Apprentice:
1
2
3
4
5
6
7
8
function read ( filename , callback ){
fs . readFile ( filename , 'utf8' , function ( err , res ){
if ( err ) {
return callback ( err );
}
callback ( null , res );
});
}
Master: OK, not bad...now process that file.
Apprentice:
1
2
3
function process ( file ){
/* Some processing stuff */
}
1
2
3
4
5
6
7
8
9
10
11
12
function read ( filename , callback ){
fs . readFile ( filename , 'utf8' , function ( err , res ){
if ( err ) {
return callback ( err );
}
try {
callback ( null , process ( res ));
} catch ( ex ) {
callback ( ex );
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
function read ( filename , callback ){
fs . readFile ( filename , 'utf8' , function ( err , res ){
if ( err ) {
return callback ( err );
}
try {
res = process ( res );
} catch ( ex ) {
return callback ( ex );
}
callback ( null , res );
});
}
Master: Takes a gasp and talks about
how do we avoid callback hell?
Name your functions.
Keep your code shallow.
Modularize!
Binding this
Apprentice: Makes a sad face.
Master: When you know...then you know!
Let us make a promise!
fulfilled
rejected
pending
settled
1
2
3
4
5
6
7
8
9
var promise = new Promise ( function ( resolve , reject ) {
// Some async...
if ( /* Allz well*/ ) {
resolve ( "It worked!" );
}
else {
reject ( Error ( "It did not work :'(" ));
}
});
1
2
3
4
5
promise . then ( function ( result ) {
console . log ( result ); // "It worked!"
}, function ( err ) {
console . log ( err ); // Error: "It don not work :'("
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async1 (). then ( function () {
return async2 ();
}). then ( function () {
return async3 ();
}). catch ( function ( err ) {
return asyncHeal1 ();
}). then ( function () {
return asyncHeal4 ();
}, function ( err ) {
return asyncHeal2 ();
}). catch ( function ( err ) {
console . log ( "Ignore them" );
}). then ( function () {
console . log ( "I'm done!" );
});
Let us talk about async map:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async . map ([ 'file1' , 'file2' , 'file3' ], fs . stat , function ( err , results ){
// results is now an array of stats for each file
});
async . filter ([ 'file1' , 'file2' , 'file3' ], fs . exists , function ( results ){
// results now equals an array of the existing files
});
async . parallel ([
function (){ ... },
function (){ ... }
], callback );
async . series ([
function (){ ... },
function (){ ... }
]);
A: Happ(Y)ily ever after?
M: No! We have some more issues:
Streams are broken, callbacks are not great to work with, errors are vague, tooling is not great, community convention is sort of there..
you may get duplicate callbacks
you may not get a callback at all (lost in limbo)
you may get out-of-band errors
emitters may get multiple “error” events
missing “error” events sends everything to hell
often unsure what requires “error” handlers
“error” handlers are very verbose
callbacks suck
Master: Let us talk about generators:
1
2
3
4
5
6
7
function * Counter (){
let n = 0 ;
while ( 1 < 2 ) {
yield n ;
n = n + 1 ;
}
}
1
2
3
4
5
6
7
8
let CountIter = Counter ();
CountIter . next ();
// Would result in { value: 0, done: false }
// Again
CountIter . next ();
//Would result in { value: 1, done: false }
1
2
3
4
5
6
7
function * fibonacci () {
let [ prev , curr ] = [ 0 , 1 ];
for (;;) {
[ prev , curr ] = [ curr , prev + curr ];
yield curr ;
}
}
1
2
3
4
5
for ( fib of fibonacci ()) {
if ( fib === 42 )
break ;
console . log ( fib );
}
1
2
3
function * powPuff () {
return Math . pow (( yield "x" ), ( yield "y" ));
}
1
2
3
4
5
6
7
let puff = powPuff ()
puff . next ();
puff . next ( 2 );
puff . next ( 3 ); // Guess ;)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function * menu (){
while ( true ){
var val = yield null ;
console . log ( 'I ate:' , val );
}
}
let meEat = menu ();
meEat . next ();
meEat . next ( "Poori" );
meEat . next ( "Pizza" );
meEat.throw(new Error("Burp!"));
1
2
3
4
5
6
7
8
9
10
function * menu (){
while ( true ){
try {
var val = yield null ;
console . log ( 'I ate: ' , val );
} catch ( e ){
console . log ( 'Good, now pay the bill :P' );
}
}
}
1
meEat . throw ( new Error ( "Burp!" ));
M: We can delegate!
1
2
3
4
5
6
7
var inorder = function * inorder ( node ) {
if ( node ) {
yield * inorder ( node . left );
yield node . label ;
yield * inorder ( node . right );
}
}
A: Confused
M: Deeper you must go! Hmmm
1
2
3
4
5
6
7
function * theAnswer () {
yield 42 ;
}
var ans = theAnswer ();
ans . next ();
A: MORE CONFUSED
M: So....
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function theAnswer () {
var state = 0 ;
return {
next : function () {
switch ( state ) {
case 0 :
state = 1 ;
return {
value : 42 , // The yielded value.
done : false
};
case 1 :
return {
value : undefined ,
done : true
};
}
}
};
}
M: But, this has not yet solved the initial issue!
M: Let us assume a function named run
~_~
1
2
3
4
5
6
7
8
9
run ( genFunc ){
/*
_ __ ___ __ _ __ _(_) ___
| '_ ` _ \ / _` |/ _` | |/ __|
| | | | | | (_| | (_| | | (__
|_| |_| |_|\__,_|\__, |_|\___|
|___/
*/
}
1
2
3
4
5
6
run ( function * (){
var data = yield read ( 'package.json' );
var result = yield process ( data );
console . log ( data );
console . log ( result );
});
1
2
3
4
5
6
7
8
9
10
11
12
13
var fs = require ( ‘ fs ’ );
function run ( fn ) {
var gen = fn ();
function next ( err , res ) {
var ret = gen . next ( res );
if ( ret . done ) return ;
ret . value ( next );
}
next ();
}
A: WOW!
M: HMM, let us talk about THUNKS!
A: Thunks??!
let timeoutThunk = (ms) => (cb) => setTimeout(cb,ms)
1
2
3
4
5
function readFile ( path ) {
return function ( callback ) {
fs . readFile ( path , callback );
};
}
Instead of :
1
readFile ( path , function ( err , result ) { ... });
We now have:
1
readFile ( path )( function ( err , result ) { ... });
So that:
1
var data = yield read ( 'package.json' );
Master: Baaazinga!
M: More usecases! Generator-based flow control.
A: Very eager.
Turn a regular node function into one which returns a thunk!
1
2
3
4
5
6
7
8
var thunkify = require ( 'thunkify' );
var fs = require ( 'fs' );
var read = thunkify ( fs . readFile );
read ( 'package.json' , 'utf8' )( function ( err , str ){
});
Write non-blocking code in a nice-ish way!
1
2
3
4
var co = require ( 'co' );
var thunkify = require ( 'thunkify' );
var request = require ( 'request' );
var get = thunkify ( request . get );
1
2
3
4
5
6
7
8
co ( function * (){
try {
var res = yield get ( 'http://badhost.invalid' );
console . log ( res );
} catch ( e ) {
console . log ( e . code ) // ENOTFOUND
}
}());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var urls = [ /* Huge list */ ];
// sequential
co ( function * (){
for ( var i = 0 ; i < urls . length ; i ++ ) {
var url = urls [ i ];
var res = yield get ( url );
console . log ( '%s -> %s' , url , res [ 0 ]. statusCode );
}
})()
// parallel
co ( function * (){
var reqs = urls . map ( function ( url ){
return get ( url );
});
var codes = ( yield reqs ). map ( function ( r ){ return r . statusCode });
console . log ( codes );
})()
M: Interesting? What more?
1
2
3
4
5
6
7
8
9
var sleep = require ( 'co-sleep' );
var co = require ( 'co' );
co ( function * () {
var now = Date . now ();
// wait for 1000 ms
yield sleep ( 1000 );
expect ( Date . now () - now ). to . not . be . below ( 1000 );
})();
1
2
3
4
5
6
7
8
9
10
11
12
var ssh = require ( 'co-ssh' );
var c = ssh ({
host : 'n.n.n.n' ,
user : 'myuser' ,
key : read ( process . env . HOME + '/.ssh/some.pem' )
});
yield c . connect ();
yield c . exec ( 'foo' );
yield c . exec ( 'bar' );
yield c . exec ( 'baz' );
1
2
3
4
5
var monk = require ( 'monk' );
var wrap = require ( 'co-monk' ); // co-monk!
var db = monk ( 'localhost/test' );
var users = wrap ( db . get ( 'users' ));
1
2
3
yield users . remove ({});
yield users . insert ({ name : 'Hemanth' , species : 'Cat' });
1
2
3
4
5
6
// Par||el!
yield [
users . insert ({ name : 'Tom' , species : 'Cat' }),
users . insert ({ name : 'Jerry' , species : 'Rat' }),
users . insert ({ name : 'Goffy' , species : 'Dog' })
];
1
2
3
4
5
6
7
var suspend = require ( 'suspend' ),
resume = suspend . resume ;
suspend ( function * () {
var data = yield fs . readFile ( __filename , 'utf8' , resume ());
console . log ( data );
})();
1
2
3
4
5
6
var readFile = require ( 'thunkify' )( require ( 'fs' ). readFile );
suspend ( function * () {
var package = JSON . parse ( yield readFile ( 'package.json' , 'utf8' ));
console . log ( package . name );
});
A: Is that all? Can I go home now?
M: No!?
M: Koa FTW?!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var koa = require ( 'koa' );
var app = koa ();
// logger
app . use ( function * ( next ){
var start = new Date ;
yield next ;
var ms = new Date - start ;
console . log ( '%s %s - %s' , this . method , this . url , ms );
});
// response
app . use ( function * (){
this . body = 'Hello World' ;
});
app . listen ( 3000 );
A: Something for the client?
M: Hmmm, good question, we have Task.js
generators + promises = tasks
1
2
3
4
5
6
7
8
9
10
11
12
13
< script type = "application/javascript" src = "task.js" >< /script>
<!-- 'yield' and 'let' keywords require version opt - in -->
< script type = "application/javascript;version=1.8" >
function hello () {
let { spawn , sleep } = task ;
spawn ( function () { // Firefox does not yet use the function* syntax
alert ( "Hello..." );
yield sleep ( 1000 );
alert ( "...world!" );
});
}
< /script>
M: Sweet and simple!
1
2
3
4
5
6
7
8
9
10
spawn ( function * () {
try {
var [ foo , bar ] = yield join ( read ( "foo.json" ),
read ( "bar.json" )). timeout ( 1000 );
render ( foo );
render ( bar );
} catch ( e ) {
console . log ( "read failed: " + e );
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var foo , bar ;
var tid = setTimeout ( function () { failure ( new Error ( "timed out" )) }, 1000 );
var xhr1 = makeXHR ( "foo.json" ,
function ( txt ) { foo = txt ; success () },
function ( err ) { failure () });
var xhr2 = makeXHR ( "bar.json" ,
function ( txt ) { bar = txt ; success () },
function ( e ) { failure ( e ) });
function success () {
if ( typeof foo === "string" && typeof bar === "string" ) {
cancelTimeout ( tid );
xhr1 = xhr2 = null ;
render ( foo );
render ( bar );
}
}
function failure ( e ) {
xhr1 && xhr1 . abort ();
xhr1 = null ;
xhr2 && xhr2 . abort ();
xhr2 = null ;
console . log ( "read failed: " + e );
}
A: Thank you master I feel enlightened!
M: Are you sure?
A: Hmmm....
M: This is just the beginning!
M: Let me talk about async-await
1
2
3
4
5
async function < name >?< argumentlist >< body >
=>
function < name >?< argumentlist > { return spawn ( function * () < body > ); }
Example of animating elements with Promise:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function chainAnimationsPromise ( elem , animations ) {
var ret = null ;
var p = currentPromise ;
animations . forEach ( function ( anim ){
p = p . then ( function ( val ) {
ret = val ;
return anim ( elem );
});
});
return p . catch ( function ( e ) {
/* ignore and keep going */
}). then ( function () {
return ret ;
});
}
Same example with task.js:
1
2
3
4
5
6
7
8
9
10
11
function chainAnimationsGenerator ( elem , animations ) {
return spawn ( function * () {
var ret = null ;
try {
for ( var anim of animations ) {
ret = yield anim ( elem );
}
} catch ( e ) { /* ignore and keep going */ }
return ret ;
});
}
Same example with async/await:
1
2
3
4
5
6
7
8
9
async function chainAnimationsAsync ( elem , animations ) {
var ret = null ;
try {
for ( var anim of animations ) {
ret = await anim ( elem );
}
} catch ( e ) { /* ignore and keep going */ }
return ret ;
}
Another example from the draft:
1
2
3
4
5
6
7
8
9
async function getData () {
var items = await fetchAsync ( 'http://example.com/users' );
return await * items . map ( async ( item ) => {
return {
title : item . title ,
img : ( await fetchAsync ( item . userDataUrl )). img
}
}
}
M: Now let us do a performance review.
A: Runs away!!
Hope you liked the play! You might also like reading Are you async yet? post.