[英]Refactoring nested callbacks, 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的異步庫,但我不確定使用哪種控制流結構。 例如,如果我使用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
和this方法,可以編寫整個函數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)
,它會填充第一個undefined
files
,以及第二個undefined
callback
,結束在調用async.map(files, readPost, callback)
。
(3)在此StackOverflow答案中還有一個partial
用於Function.prototype
的版本,允許您使用以下語法: async.map.partial(undefined, readPost, undefined)
; 但是,我可能會建議不要以這種方式修改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))
}
我最近創建了一個名為WaitFor的簡單抽象來在同步模式下調用異步函數(基於Fibers): 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);
});
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.