Async

Evolutionary History

Created by Howard.Zuo / @leftstick

How to deal with async task?

Callback-based flow

Callback is good


getSession(function(session){
    console.log('userId is', session.userId);//handle async process easily
});
    					

Callback is not so good


getSession(function(session){
    getCurrentUser(session.userId, function(user){
        getUserInfo(user.bestFriendId, function(friend){
            getPosts(friend.id, function(posts){
                console.log('posts are', posts);//tricky?
            });
        });
    });
});
						
We will use this example in the whole slides, so keep it in mind would be good^^

What is Thunk

Background

Many years ago, while computer scientists were trying to figure out which the best evaluation strategies is, two options are available

  • Call by value - calculates arguments before the call and then passes the resulting values to the subroutine
  • Call by name - the subroutine receives the unevaluated argument expression and then evaluate it at proper time

Call by value


'use strict'
var x = 1;
var sum = function(m){
    return m + 5;
};

//call by value means following two calls are the same
sum(x + 2);
sum(3);
						
Normally, we believe this cost a bit more, since the expression gets evaluated before it really used

Call by name


'use strict'
var x = 1;
var sum = function(m){
    return m + 5;
};

//call by name means
sum(x + 2);
//is the same as following
(x + 2) + 5;
						
It might produce multiple versions of the subroutine and multiple copies of the expression code by simply substitute the code of an argument expression for each appearance of the corresponding parameter in the subroutine

Thunk was born

A helper subroutine, called thunk, that calculates the value of the argument
Let's think of it as 'temporary function expression'

Thunk


'use strict'
var x = 1;

var sum = function(exp){
    return exp() + 5;
};

//extract the expression into a function
//pass the function as argument into sum
var thunky = function () {
  return x + 2;
};

sum(thunky);
						
What does this have to do with async? hold your horses, let's move on

Async with Thunk

Most async JavaScript APIs accept arguments, and end up with an optional callback


'use strict'
var fs = require('fs');

fs.readFile('/etc/hosts', 'utf-8', function(err, data){
    console.log('the content in hosts is', data);
});
						
Image what can we do with Thunk?

'use strict'
var fs = require('fs');

//we call this function as "thunkify"
var readFile = function(fileName, options){
    return function(callback){
        return fs.readFile(fileName, options, callback);
    };
};

var thunky = readFile('/etc/hosts', 'utf-8');
//accept callback as single argument
thunky(callback);
						
You may say "this makes my code even more complex"
Don't worry, tj had written a cool library for you, It is thunkify, let's refactor a bit with this thunkify

Refactor with Thunkify


'use strict'
var thunkify = require('thunkify');
var fs = require('fs');

var readFile = thunkify(fs.readFile);

var thunky = readFile('/etc/hosts', 'utf-8');

thunky(callback);
						
You may ask: "How does this help my async code?"

I would say: "Nothing useful without Generator"

But let's take a look Promise first?

Doesn Promise help?


'use strict'

getSession()
.then(function(session){
    return getCurrentUser(session.userId);
})
.then(function(user){
    return getUserInfo(user.bestFriendId);
})
.then(function(friend){
    return getPosts(friend.id);
})
.then(function(posts){
    console.log('posts are', posts);
})
.catch(function(err){
    console.error('ERROR', err);
});
						
This is better than callback hell, right?
Once you'd like to know how Promise achieved this, here is the place

Promiseify is required


'use strict'

var fs = require('fs');

//most node APIs are callback-based
//But we love something like this
fs.readFile('/etc/hosts', 'utf-8')
.then(function(data){
    console.log('the content in hosts is', data);
})
.catch(function(err){
    console.error('ERROR', err);
});
						

Let me introduce Promiseify

Let's see how it helps on above code

Promisesify


'use strict'

var fs = require('fs');
var promiseify = require('just-promiseify');

var readFile = promiseify(fs.readFile);//usage just like thunkify

readFile('/etc/hosts', 'utf-8')
.then(function(data){
    console.log('the content in hosts is', data);
})
.catch(function(err){
    console.error('ERROR', err);
});
						
Turn a regular callback-based function into one which returns a Promise

Is Promise the end?

No actually, generator improves our code even further

Write as synchronized?


'use strict'

try{
    var session = getSession();
    var user = getCurrentUser(session.userId);
    var friend = getUserInfo(user.bestFriendId);
    var posts = getPosts(friend.id);
    console.log('posts are', posts);
}catch(e){
    console.error('ERROR', err);
}
						
It's not a dream with generator + co
Let's refactor a bit

With co


'use strict'
var co = require('co');
var execute = function*() {//this is a generator

    try {//the thing right after yield, we call them "yieldables"
        var session = yield getSession();
        var user = yield getCurrentUser(session.userId);
        var friend = yield getUserInfo(user.bestFriendId);
        var posts = yield getPosts(friend.id);
        console.log('posts are', posts);
    } catch (e) {
        console.error('ERROR', err);
    }
};

co(execute);
						
yield allows thunk and Promise, that's why we introduce Thunk earily this slides

ES7 - async

async is syntactic sugar for generator, the usage is similar. Few advantages:

  • built-in executor, which mean no co any more
  • more readable, async and await are more clear than * and yield
  • more robust, await allows more types
Let's refactor a bit with previous generator version

ES7 version


'use strict'

var execute = async function() {//this is async function

    try {//the thing right after yield, we call them "yieldables"
        var session = await getSession();
        var user = await getCurrentUser(session.userId);
        var friend = await getUserInfo(user.bestFriendId);
        var posts = await getPosts(friend.id);

        console.log('posts are', posts);
    } catch (e) {
        console.error('ERROR', err);
    }
};

execute();
						

THE END

- Thunkify
- Promiseify
- co
- Babel