web-dev-qa-db-ja.com

Node.jsのフロー制御ソリューション-async.waterfallを使用しますか?

Node.jsを確認し、コールバックのネストを回避します。 Async.jsを見つけました。async.waterfallメソッドを使用してこのコードを書き換える方法を誰かに教えてもらえますか?

// db is a mongodb instance
db.open(function(err, client){
    client.createCollection("docs", function(err, col) {
         client.collection("docs", function(err, col) {
             for (var i = 0; i < 100; i++) {
                 col.insert({c:i}, function() {});
             }
             console.log('Insert done OK.');
         });
    });
});

内部関数を使用したこの単純な解決策には、非同期メソッドにどのような問題がありますか?

db.open(openAndInsert);

function openAndInsert(err, client) {
    var myCollection = "docs";

    client.createCollection(myCollection, openCollection);

    function openCollection(err, col) {
        client.collection(myCollection, insertIntoCollection);
    }

    function insertIntoCollection(err, col) {
        for (var idx = 0; idx < 100; idx += 1) {
            col.insert({c:idx}, function () {});
        }
        console.log('Insert done OK.');
    }
}

元のコードの入れ子の3つのレベルはコードのにおいのように見えますが、ホームロールまたは非同期ソリューションのどちらが全体的に優れているかはわかりません。入れ子関数をそのままにすることを好む人はいますか?もしそうなら、あるタイプのフロー制御を使用する前に、ネストはどのくらい深くなければなりませんか?

4
Sean

あなたはいくつかの質問をしました、私は最後の2つに答えます。

無名関数を使用したコールバックのネストは、コードの再利用とDRYの反対です。コールバックを真ん中に置くことでロジックを1つの関数に分割することにより、コードを読みにくくします。

通常、コールバックに使用するより一般的な関数を作成しようとすると、さまざまな状況(エラーなど)で何をすべきかを理解しようとするだけで問題が発生します。ネストされた関数呼び出しとすべてを説明しようとするスパゲッティコードの束を作成することになります。

コールバックがあります。それがnodejsの性質です。

Asyncjsやqcnodeのようなものを使用すると、より理解しやすくデバッグしやすい線形的な方法でコードを記述できます。

エラーを処理し、実行しようとしていることを続行できるかどうかを判断する小さなスタブコールバックが必要です。それ以外の場合は中止する必要があります。私は何年にもわたってqcnode(およびその祖先)を使用してきましたが、この最新バージョンは使いやすく、非常に役立つと思います。

完全な開示:私は qcnode のパブリッシャーおよび主要開発者です。

1
JoshRagem

これは、async.jsを使用するために必要なことを行います。

db.open(function (err, client) {
    async.waterfall([
        function (callback) {
            client.createCollection("docs", callback);
        },
        function (collection, callback) {
            for (var i = 0; i < 100; i++) {
                collection.insert({c:i}, function() {});
            }
            callback(null, 'ok');
        }
    ],
    function (err, result) {
        console.log('result: ', result);
    });
});

非同期コードをdb.openに渡された匿名関数内に配置することにより、「クライアント」変数が最初の非同期関数に表示されました。 this SO answer からそのアイデアを得ました

0
Sean

個別の関数と非同期の両方をお勧めします...また、Function.prototype.bindは非常に強力であることを覚えておいてください。

_//open the connection first, because the client may need to be used for cleanup.
db.open(function(err, client){

    //run the tasks in series, passing the result of each to the next task
    async.waterfall([
        //bind collection method to run against "docs"
        //there is no need to create a wrapper function yourself for this
        client.collection.bind(client, "docs")

        //passes the resulting collection to addToDocs
        ,addToDocs 

    ], completed); //run completed function at the end

    //will add 100 docs into the database
    function addToDocs(docs /*collection*/, cb) {
        //list of items to be run (async/parallel)
        var todo = [];
        for (var i=0; i<100; i++) {
            //bind the insert method against a new object for i, so it can be run later
            todo.Push(docs.insert.bind(col, {c:i}));
        }

        //do all the inserts, then run the original callback
        async.parallel(todo, cb);

        //alternatively, you could have used async.series(todo, cb) to run one at a time.
    }

    function completed(err, results) {
        //close client resources, return connection to pool etc

        //handle any errors
        if (err) {
            console.log(err);
        }

        //any other work to be done
    }
});_

私はたまたまこのパターンが好きです。あなたはあなたのロジックが一番上にあり、参照メソッドが下にあります...

注:関数宣言のみが適切に機能するように巻き上げられ、var foo = function(){...}は巻き上げられません。

0
Tracker1