Performance test of async / await in Node.js 8 LTS

The recently released Node.js 8 LTS comes with async / await support, which turns promises from an ugly kludge into something actually useful.

Normally, I am a big fan of callbacks and will continue to use them as a first choice, but there is a particular use-case where promises are nice to have: chaining together multiple asynchronous API requests. While it is possible to make the code look reasonably nice by splitting each callback into stand-alone functions, it may be preferable to have the logic presented in a synchronous looking manner, for easier reading.

This post shows an example of using the new async / await feature and compares performance to a traditional callback implementation.

TL;DR

When tested against an HTTP server running on localhost the promises implementation was about 2% slower, which is, for the most part, insignificant. :)

Benchmark

The test scenario involves making three HTTP requests in sequence, using the result of the previous request as the path for the next one. This is a simplified version of a standard real-life use-case of assembling some data from several different API endpoints.

Callback version

const http = require('http');

function makeRequest(path, callback) {
    const o = {
        method: 'GET',
        port: 8080,
        path: path
    };

    const req = http.request(o, function (res) {
        let data = '';

        res.on('data', function (buffer) {
            data += buffer.toString();
        });

        res.on('end', function () {
            callback(undefined, data);
        });
    });

    req.on('error', function (err) {
        callback(err);
    });

    req.end();
}

function getData (callback) {
    makeRequest('/a', function (err, path) {
        if (err) {
            return callback(err);
        }

        makeRequest(path, function (err, path) {
            if (err) {
                return callback(err);
            }

            makeRequest(path, function (err, path) {
                if (err) {
                    return callback(err);
                }

                callback(undefined, path);
            });
        });
    });
}

Promise version

const http = require('http');

function makeRequest(path) {
    return new Promise(function (resolve, reject) {
        const o = {
            method: 'GET',
            port: 8080,
            path: path
        };

        const req = http.request(o, function (res) {
            let data = '';

            res.on('data', function (buffer) {
                data += buffer.toString();
            });

            res.on('end', function () {
                resolve(data);
            });
        });

        req.on('error', function (err) {
            reject(err);
        });

        req.end();
    });
}

async function getData (callback) {
    var path;

    try {
        path = await makeRequest('/a');
        path = await makeRequest(path);
        path = await makeRequest(path);

    } catch (e) {
        callback(e);
    }

    callback(undefined, path);
}

Benchmark code

const results = [];

(function loop (i) {
    if (i) {
        let start = Date.now();

        getData(function (err) {
            if (err) {
                throw err;
            }

            results.push(Date.now() - start);

            process.nextTick(function () {
                loop(i - 1);
            });
        });

    } else {
        console.log(results.slice(10).reduce(function (sum, n) {
            return sum + n;
        }, 0));
    }
}(100010));

HTTP server code

var http = require('http');

const server = http.createServer(function (req, res) {
    if (req.url === '/a') {
        res.end('/b');
    } else if (req.url === '/b') {
        res.end('/c');
    } else if (req.url === '/c') {
        res.end('/d');
    }
});

server.listen(8080);

Results

Node version 8.9.1 was used in this test.

Callback version

51616
51504
52370
51137
51477
51574
51818
51019
51053
51458

Avg: 51502.6

Promise version

52701
52447
52264
52816
52913
52637
52104
52659
52193
52330

Avg: 52506.4