Domanda

I am asking myself when it makes sense to be async with callbacks on node.js. Is it just usefull when I am working with I/O, Databases and everything else what blocks or also in the rest of my code?

For example, I got a "layouter" for a board game. It creates a board instance with a given json layout. I understand that it makes sense to use async for I/O (reading the json layout file), because that blocks. Whats about for example with a foreach loop? Should it be async? Does the underlaying libuv profit in any way from this?

Here is the version with the async lib:

/*jshint node: true, strict: true, globalstrict: true*/
"use strict";

// Imports
var fs = require('fs');
var async = require('async');
var Figure = require('../models/figure.js');
var Field = require('../models/field.js');
var Board = require('../models/board.js');

/**
 * @param {Board} board
 * @param {Array} template
 * @param {string} owner
 * @param {function(Error)} callback
 */
var createFigures = function (board, template, owner, callback) {
    async.forEach(template, function (data, done) {
        var figure = new Figure(owner, data.kind);
        board.setFigure(data.x, data.y, figure);
        done();
    }, callback);
};

/**
 * @param {Board} board
 * @param {Array} fields
 */
var createFields = function (board, fields, callback) {
    async.series([
        function(done) {
            for (var x = 0; x < board.getSize().x; x++) {
                for (var y = 0; y < board.getSize().y; y++) {
                    var field = new Field(true, false);
                    board.setField(x, y, field);
                }
            }
            done();
        }, function(done) {
            async.forEach(fields, function(data, iteratDone) {
                var field = new Field(data.passable, data.corner);
                board.setField(data.x, data.y, field);
                iteratDone();
            });
            done();
        }
    ], callback);
};

/**
 * @param {{
 *  ....
 */
var build = function (layoutObj, callback) {
    var board = new Board(layoutObj.name, layoutObj.size.x, layoutObj.size.y);

    async.parallel([
        function (done) {
            createFigures(board, layoutObj.figures.white, Figure.owners.white, done);
        },
        function (done) {
            createFigures(board, layoutObj.figures.black, Figure.owners.black, done);
        },
        function (done) {
            createFields(board, layoutObj.fields, done);
        }
    ],
        function (err) {
            callback(err, board);
        }
    );
};

/**
 * @param string layoutPath
 * @param {function(Error, )} callback
 * @return {Board}
 */
var generateBoardWithLayout = function (layoutPath, callback) {
    async.waterfall([
        function(done) {
            fs.stat(layoutPath, function(err, stats) {
               done(null, stats);
            });
        }, function(stats, done) {
            if (stats.isFile()) {
                var jsonData = '';
                fs.readFile(layoutPath, function(err, data) {
                    jsonData += data;
                    done(null, jsonData);
                });
            } else {
                done(new Error("There is no '" + layoutPath + "'"));
            }
        }
    ], function(err, jsonData) {
        build(JSON.parse(jsonData), callback);
    });

};

module.exports.build = build;
module.exports.generateBoardWithLayout = generateBoardWithLayout;

And thats the version without the async link

/*jshint node: true, strict: true, globalstrict: true*/
"use strict";

// Imports
var Figure = require('../models/figure.js');
var Field = require('../models/field.js');
var Board = require('../models/board.js');

/**
 * @constructor
 */
function Layouter() {

}

Layouter.generateBoardWithLayout = function(layoutPath) {
    var fs = require('fs');

    var jsonData = fs.readFileSync(layoutPath);
    if (!jsonData) {
        throw new Error("There is no '" + layoutPath + "'");
    }

    return new Layouter().build(JSON.parse(jsonData));
};

Layouter.prototype = {

    /**
     * @param {{
     *     ....
     */
    build: function (layoutObj) {
        var board = new Board(layoutObj.name, layoutObj.size.x, layoutObj.size.y);

        this.createFigures_(board, layoutObj.figures.white, Figure.owners.white);
        this.createFigures_(board, layoutObj.figures.black, Figure.owners.black);
        this.createFields_(board, layoutObj.fields);

        return board;
    },

    /**
     * @param {Board} board
     * @param {Array} template
     * @param {string} owner
     * @private
     */
    createFigures_: function (board, template, owner) {
        template.forEach(function (data) {
            var figure = new Figure(owner, data.kind);
            board.setFigure(data.x, data.y, figure);
        });
    },

    /**
     * @param {Board} board
     * @param {Array} fields
     * @private
     */
    createFields_: function (board, fields) {
        for (var x = 0; x < board.getSize().x; x++) {
            for (var y = 0; y < board.getSize().y; y++) {
                var field = new Field(true, false);
                board.setField(x, y, field);
            }
        }

        fields.forEach(function (data) {
            var field = new Field(data.passable, data.corner);
            board.setField(data.x, data.y, field);
        });
    }

};

module.exports = Layouter;
module.exports.generateBoardWithLayout = Layouter.generateBoardWithLayout;

Thanks for your time!

Greetings, Markus

È stato utile?

Soluzione

I think you miss idea behind asynchronicity in Node.js. It's not about using async package (which is just npm package that can be used with Node, and not Node's internal package), but about processing external processes in asynchronous way. e.g. when you query the database, you may do a lot of stuff just while waiting for result, that's the deal.

If your code doesn't query any external processes but it's plain JavaScript, then with single process you can't benefit from asynchronicity directly (Node.js is single threaded), what you may do however is to delegate some work to different process and communicate with it (see child_process.fork).

Altri suggerimenti

looks like you just make new object instances without doing any I/O, in this case u are unnecessarily complicating the code, it is ugly. You can only continue your program in the last callback function, which limit your entire flow into one single big async call.

Normally your program flow should not depend on its previous async callback to excute first, in you code

createFigure();  
createField();

both are one single async call, what if something in createField depend on createFigure to be excuted first, in this case the behavior is undefined. Generally, don't use async unless you are doing blocking operations

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top