重构嵌套的callback,node.js,async

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的asynchronous库,但我不确定使用哪个控制stream结构。 看来,如果我使用async.waterfall,例如,会导致更多的代码,每个步骤都必须包装在一个匿名函数中。 例如,这只是瀑布的嵌套版本的前两行:

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

这是一个有趣的问题,因为您需要将参数绑定到您的迭代器函数的左侧和右侧,所以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)如果你真的想避免包装function,你可以使用一种types的部分function应用程序。 首先,在文件的顶部(或者在一个模块等),定义一个名为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); }; } 

这个函数接受一个函数和任意数量的参数,并且在函数被调用时用实际参数replace参数列表中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)返回一个函数,当它被partial(async.map, undefined, readPost, undefined)库调用为fn(files, callback) ,会为第一个undefined files填充files ,为第二个undefined files填充files在调用async.map(files, readPost, callback)

(3)在这个StackOverflow答案中 ,还有一个用于Function.prototypepartial的版本,允许你使用下面的语法: async.map.partial(undefined, readPost, undefined) ; 不过,我可能会build议不要这样修改Function.prototype ,只是使用partial作为函数。

最后,由您决定哪种方法最具可读性和可维护性。

看起来我和布兰登的答案有一些重叠,但这是我的看法:

  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) 

这是迄今为止我已经结束了。

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

我最近创build了一个名为WaitFor的简单抽象,以同步模式调用asynchronous函数(基于Fibers): https : //github.com/luciotato/waitfor

我没有用asynchronous包testing它,但它应该工作。 如果遇到问题,请联系我。

使用wait.for和asynchronous你的代码将是:

 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(asynchronous模式):

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