6
function indexArticles(callback) {
  fs.readdir("posts/", function(err, files) {
    async.map(files, readPost, function(err, markdown) {
      async.map(markdown, parse, function(err, results) {
        async.sortBy(results, function(obj, callback) {
          callback(err, obj.date);
        }, function(err, sorted) {
          callback( {"articles": sorted.reverse()} );
        });
      });
    });
  });
}

私はこれをより美しくする方法を見つけようとしています-あなたが言うことができるように、私はcaolanの非同期ライブラリを使用していますが、どの制御フロー構造を使用するかはわかりません。たとえば、async.waterfallを使用すると、かなり多くのコードが生成され、各ステップを無名関数でラップする必要があるようです。たとえば、これはウォーターフォールのあるネストされたバージョンの最初の2行にすぎません。

function indexArticles(callback) {
  async.waterfall([
    function(callback) {
      fs.readdir("posts/", function(err, files) {
        callback(err, files)
      })
    },

    function(files, callback) {
      async.map(files, readPost, function(err, markdown) {
        callback(err, markdown)
      })
    }])
}

これをどのように改善しますか?

左からだけでなく、引数を部分的に適用する方法があれば、たとえば、

function indexArticles(callback) {
  async.waterfall([
    async.apply(fs.readdir, "posts/"),
    async.apply(async.map, __, readPost),
    async.apply(async.map, __, parse),
    // etc...
  ])
}
4

4 に答える 4

6

これは興味深い問題です。イテレータ関数の左側と右側の両方に引数をバインドする必要があるため、bind/もbindRight(StackOverflowにいくつかの実装があります)どちらも機能しません。ここにはいくつかのオプションがあります。

(1)まず、あなたのasync.waterfall例では、次のようになります。

function(callback) {
  fs.readdir("posts/", function(err, files) {
    callback(err, files)
  })
}

これは次と同じです:

function(callback) {
  fs.readdir("posts/", callback)
}

Function.bindこのメソッドを使用すると、関数全体を次のindexArticlesように記述できます。

function indexArticles(callback) {
  async.waterfall([
    fs.readdir.bind(this, 'posts/'),
    function(files, cb) { async.map(files, readPost, cb); },
    function(text, cb) { async.map(text, parse, cb); },
    function(results, cb) { async.sortBy(results, function(obj, callback) {
      callback(null, obj.date);
    }, cb) }
  ], function(err, sorted) {
    callback( {"articles": sorted.reverse()} );
  });
};

これは少し短いです。

(2)本当にラッピング関数を避けたい場合は、ある種の部分関数アプリケーションを使用できます。まず、ファイルの先頭(またはモジュールなど)で、次の関数を定義しますpartial

var partial = function(fn) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function() {
    var currentArg = 0;
    for(var i = 0; i < args.length && currentArg < arguments.length; i++) {
      if (args[i] === undefined)
        args[i] = arguments[currentArg++];
    }
    return fn.apply(this, args);
  };
}

undefinedこの関数は、関数と任意の数の引数を取り、関数が呼び出されたときに引数リストの値を実際の引数に置き換えます。次に、次のように使用します。

function indexArticles(callback) {
  async.waterfall([
    fs.readdir.bind(this, 'posts/'),
    partial(async.map, undefined, readPost, undefined),
    partial(async.map, undefined, parse, undefined),
    partial(async.sortBy, undefined, function(obj, callback) {
      callback(null, obj.date);
    }, undefined)
  ], function(err, sorted) {
    callback( {"articles": sorted.reverse()} );
  });
}

したがって、 Asyncライブラリによってとして呼び出されたときに、最初の、および2番目のを埋めて、の呼び出しで終わるpartial(async.map, undefined, readPost, undefined)関数を返します。fn(files, callback)filesundefinedcallbackundefinedasync.map(files, readPost, callback)

(3)このStackOverflowの回答partialにはforFunction.prototypeのバージョンもあり、構文を使用できます。ただし、この方法で変更するのではなく、関数として使用することをお勧めします。async.map.partial(undefined, readPost, undefined)Function.prototypepartial

結局、どの方法が最も読みやすく、保守しやすいかはあなた次第です。

于 2012-07-17T02:31:31.377 に答える
2

ブランドンの答えと一部重複しているように見えますが、これが私の見解です。

 var async = require("async")

//dummy function
function passThrough(arg, callback){
  callback(null, arg)
}

//your code rewritten to only call the dummy. 
//same structure, didn't want to think about files and markdown
function indexArticles(callback) {
  passThrough("posts/", function(err, files) {
    async.map(files, passThrough, function(err, markdown) {
      async.map(markdown, passThrough, 
        function(err, results) {
          async.sortBy(results, function(obj, callback) {
            callback(err, obj);
        }, 
        function(err, sorted) {
          callback( {"articles": sorted.reverse()} );
        });
      });
    });
  });
}
indexArticles(console.log)

//version of apply that calls 
//fn(arg, arg, appliedArg, apliedArg, callback)
function coolerApply(fn) {
  var args = Array.prototype.slice.call(arguments, 1);
  return function () {
    var callback = Array.prototype.slice.call(arguments, -1)
    var otherArgs = Array.prototype.slice.call(arguments, 0, -1)
    return fn.apply(
      null, otherArgs.concat(args).concat(callback)
    );
  };
};

//my version of your code that uses coolerAppl
function indexArticles2(callback){
  async.waterfall([
    async.apply(passThrough, "posts/"),
    coolerApply(async.map, passThrough),
    coolerApply(async.map, passThrough),
    coolerApply(async.sortBy, function(obj, callback){callback(null,obj)})
  ],
  function(err, sorted){
    callback({"articles": sorted.reverse()})
  })
}
//does the same thing as indexArticles!
indexArticles2(console.log)
于 2012-07-17T02:44:38.333 に答える
1

これが私がこれまでに終わったものです。

function indexArticles(callback) {
  var flow = [
    async.apply(fs.readdir, "posts/"),

    function(data, callback) { async.map(data, readPost, callback); },

    function sortByDate(parsed, callback) {
      var iterator = function(obj, callback) {
        if (obj.date) { callback(null, obj.date); }
        else { callback("Article has no date.") }
      }
      // Note that this sorts in reverse lexicographical order!
      async.sortBy(parsed, iterator,
          function(err, sorted) { callback(err, {"articles": sorted.reverse()} ); }
        );
    }
  ];

  async.waterfall(flow, async.apply(callback))
}
于 2012-07-17T05:56:23.877 に答える
1

最近、WaitForという名前の単純な抽象化を作成して、同期モードで非同期関数を呼び出しました(ファイバーに基づく):https ://github.com/luciotato/waitfor

非同期パッケージでテストしていませんが、動作するはずです。問題が発生した場合は、私に連絡してください。

wait.forとasyncを使用すると、コードは次のようになります。

var wait = require('waitfor');
var async = require('async');

function indexArticles(callback) {
  var files = wait.for(fs.readdir,"posts/");
  var markdown = wait.for(async.map, files, readPost);
  var results = wait.for(async.map, markdown, parse);
  var sorted = wait.for(async.sortBy, results, function(obj, callback) {
                                                  callback(null, obj.date);
                                              });
  callback( null, {"articles": sorted.reverse()} );
}

fn(非同期モード)を呼び出すには:

//execute in a fiber
wait.launchFiber(indexArticles,function(err,data){
       // do something with err,data
       }); 

fn(同期モード)を呼び出すには:

//execute in a fiber
function handleRequest(req,res){
    try{
        ...
        data = wait.for(indexArticles); //call indexArticles and wait for results
        // do something with data
        res.end(data.toString());
    }
    catch(err){
        // handle errors
    }
}

// express framework
app.get('/posts', function(req, res) { 
    // handle request in a Fiber, keep node spinning
    wait.launchFiber(handleRequest,req,res);
    });
于 2013-08-13T00:30:28.770 に答える