Typescript 1.6 and async/await

One of the best features in C# that is difficult to find in other languages is the ability to write asynchronous code without having to do callbacks. This makes the code flow much more “natural” and sometimes, easier to debug. For example, imagine the following code in NodeJS:

function RequestHandler(){
    DatabaseCall1(function(){
        NetworkCall1(function(){
            DatabaseCall2(function(){
                NetworkCall2(function(){
                     Response.Return();
                });
            });
        });
    });
}

Obviously that code block is not nice at all. Such a pattern is often called “the pyramid of doom“, making the code looks more complicated that it really is.
In C#, asynchronous programming is implemented so that developers could write codes which are much more “natural”, especially when executing asynchronous functions sequentially:

public async Task RequestHandler(){
    await DatabaseCall1();
    await NetworkCall1();
    await DatabaseCall2();
    await NetworkCall2();
    Response.Return();
}

Async/await is a planned feature for javascript (expected to be in ES7). Given the current state of javascript when ES6 has not been fully implemented yet, it would take quite a few years for this feature to be brought to life.

TypeScript to the rescue

Typescript is an open-sourced project from Microsoft (yes, open-source) that provides(optional) strong typings for javascript. While the main goal is to make writing large applications easier by supplying type information(classes, value types, interfaces…) , it also enables lots of future javascript features , for example, modules, generators, decorators… Typescript can be installed via npm. Visual Studio and Visual Studio Code has built-in support for typescript, and there are plug-ins for Sublime, Atom, and Webstorm… as well!

Typescript 1.6 brings async/await as an “experimental” feature, which means it’s still in development. Nonetheless, it does work. We’ll look at how to enable this feature for NodeJS. Making this work on browsers is a little bit more troublesome, which will not be covered here.

Async/Await in TS 1.6

First off, we need to config the typescript compiler. Every typescript project has a configuration file named “tsconfig.json” placed in the root directory of the project. The following is a configuration file that enables async/await:

{
    "version": "1.6.2",
    "compilerOptions": {
        "experimentalDecorators": true,
        "experimentalAsyncFunctions": true,
        "sourceMap": true,
        "target": "ES6"
    },
    "files": [
        "app.ts"
    ]
}

You’ll need typescript version 1.6.2 or higher. Also make sure that you have NodeJs >4.0 installed. The “experimentalAsyncFunctions” option needs to be enabled and target must be “ES6″(or higher). The “files” option is a list of files to be compiled. By default, Typescript will compile {filename}.ts to {filename}.js. In this example, there is a single file called app.ts. Open that file and we can start writing async functions:

async function DoWorkAsync() : Promise<string>{
    var result = "";
    for(var i=0;i<100;i++){
        result += i.ToString();
    }
    return result;
}

async function EventHandler(){
    var result = await DoWorkAsync();
    console.log(result);
}

As you can see, EventHandler is an async function that can “await” for another async function to return. An async function is a function that returns a Promise, which is basically a wrapper of some data that will be available in the future(or not). The async function DoWorkAsync returns a Promise of type <string>, which tells the await keyword to assign the return value of that async operation to the “result” variable, and that value is of type “string”. Notice that in DoWorkAsync, you don’t actually have to return a Promise, typescript will automatically convert the return value to Promise .

So how do we apply async/await scheme for those NodeJS modules that use the callback pattern? We could write a wrapper method that returns a Promise which will resolve during the callback phase. For example, the following code make turn the http.get() method into an async function:

class HttpResponse {
    public StatusCode: number;
    public Content: Buffer;
    constructor() {
        this.StatusCode = 200;
        this.Content = null;
    }
}

class HttpRequest {
    public GetAsync(url: string): Promise<HttpResponse> {
        return new Promise<HttpResponse>((resolve, reject) => {
            var response = new HttpResponse();
            http.get(url, function(res) {
                response.StatusCode = res.statusCode;
                var body = "";
                res.on('data', function(d) {
                    body += d;
                });
                res.on('end', function() {
                    response.Content = new Buffer(body);
                    resolve(response);
                });
            }).on('error', function(e) {
                response.StatusCode = 500;
                response.Content = e.message;
                resolve(response);
            });
        });
    }
}

The GetAsync method is a method that actually returns a Promise of an HttpResponse instance. The “response” object is resolved when the data is fully retrieved, or when error happens. Other async functions can now call “await HttpRequest.GetAsync()” to get an HttpResponse instance.

Finally, let’s take the classes we just wrote and implement an asynchronous web server.

http.createServer(async function(req, res) {
    var httpRequest = new HttpRequest();
    var response1 = await httpRequest.GetAsync("http://jsonplaceholder.typicode.com/posts/1");
    var response2 = await httpRequest.GetAsync("http://jsonplaceholder.typicode.com/posts/2");
    var serverResponse = {
        codes: [response1.StatusCode, response2.StatusCode],
        bodies: [JSON.parse(response1.Content.toString()), JSON.parse(response2.Content.toString())]
    }
    res.setHeader("Content-Type", "application/json");
    res.end(JSON.stringify(serverResponse));
}).listen(5000, function() {
    console.log("Our glorious server runs");
});

Notice that the request handler itself is an async function. Otherwise, Typescript will throw an error since you cannot “await” in a function that is not “async

Bình luận về bài viết này