2012-07-17 109 views
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,例如,會導致更多的代碼,每一步都必須包裝在一個匿名函數中。例如,這只是瀑布的前兩行嵌套版本:重構嵌套回調,Node.js的,異步

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... 
    ]) 
} 
+0

雖然'瀑布'可能會結束更多的字符,我認爲它會結束更可讀性。同時檢查'apply'以幫助完成所有這些匿名功能。 – 2012-07-17 00:45:45

+0

你可以看看我剛發佈的瀑布例子,告訴我是否正確嗎? – 2012-07-17 01:04:31

回答

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()}); 
    }); 
} 

所以,partial(async.map, undefined, readPost, undefined)返回,當由異步庫作爲fn(files, callback)叫,它files填補了第一undefined第二undefined的功能,並callback,在結束致電async.map(files, readPost, callback)

(3)對於Function.prototype,還有partialthis StackOverflow answer版本,允許您使用以下語法:async.map.partial(undefined, readPost, undefined);不過,我可能會建議不要以這種方式修改Function.prototype,而只是使用partial作爲函數。

最後,由您決定哪種方法最具可讀性和可維護性。

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) 
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)) 
} 
1

我剛剛創建(基於纖維)命名WaitFor的調用在同步模式異步功能的簡單抽象:https://github.com/luciotato/waitfor

我沒有與異步封裝測試,但它應該工作。如果遇到問題,請與我聯繫。

使用wait.for和異步代碼將是:

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); 
    });