2012-07-17 11 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()}); 
     }); 
     }); 
    }); 
    }); 
} 

estoy tratando de encontrar la manera de hacer esto más bonita - como se puede decir que estoy usando la biblioteca asíncrona de Caolan, pero no estoy seguro de cuál de las controlar estructuras de flujo para usar. Parece que si uso async.waterfall, por ejemplo, resulta en un poco más de código, y cada paso debe estar envuelto en una función anónima. Por ejemplo, estas son solo las dos primeras líneas de la versión anidada con cascada:devoluciones de llamadas anidadas Refactoring, Node.js, asincrónicos

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

¿Cómo mejorarías esto?

Si hubiera una manera de aplicar parcialmente argumentos no sólo de la izquierda, yo podía ver haciendo, por ejemplo,

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

Si bien 'waterfall' podría terminar con más caracteres, creo que terminará siendo mucho más legible. También consulte 'apply' para ayudar con todas esas funciones anon. –

+0

¿Puedes echar un vistazo al ejemplo de la cascada que acabo de publicar y decirme si lo estoy haciendo bien? –

Respuesta

6

Este es un problema interesante, ya que necesita de obligar argumentos tanto a la a la izquierda y a la derecha de sus funciones de iterador, por lo que ni bind/ni bindRight (de las cuales hay algunas implementaciones en StackOverflow) funcionarán para usted. Hay algunas opciones para usted aquí:

(1) En primer lugar, en su ejemplo async.waterfall, usted tiene:

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

que es lo mismo que:

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

Usando Function.bind y este método , toda su función indexArticles se podría escribir:

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

que es un poco más corto.

(2) Si realmente desea evitar las funciones de envoltura, puede utilizar un tipo de aplicación de función parcial. En primer lugar, en la parte superior de su archivo (o en un módulo, etc), definir una función llamada 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); 
    }; 
} 

Esta función tiene una función y cualquier número de argumentos, y sustituye undefined valores en la lista de argumentos con los argumentos reales cuando se llama a la función. A continuación, utilizar de esta manera:

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

Así, partial(async.map, undefined, readPost, undefined) devuelve una función que, cuando se le llama por la biblioteca asíncrono como fn(files, callback), se llena en files por primera undefined, y callback para la segunda undefined, que termina en una llamada al async.map(files, readPost, callback).

(3) También hay una versión de partial para Function.prototype en this StackOverflow answer, lo que permite utilizar la sintaxis: async.map.partial(undefined, readPost, undefined); sin embargo, probablemente recomendaría no modificar Function.prototype de esta manera, y simplemente usar partial como una función.

Al final, depende de usted cuál es el método más legible y mantenible.

2

parece que tengo cierta superposición con la respuesta de Brandon, pero aquí está mi opinión:

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

Esto es lo que he terminado con hasta el momento.

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

que he creado recientemente una simple abstracción llamada WaitFor para llamar a funciones asíncronas en el modo de sincronización (a base de fibras): https://github.com/luciotato/waitfor

yo no lo he probado con el paquete asíncrono, pero debería funcionar . Si tiene problemas, contácteme.

Uso wait.for y asíncrono su código será:

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

para llamar a su fn (asíncrono modo):

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

para llamar a su fn (modo de sincronización):

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