2011-12-20 5 views
6

Soy nuevo en Node y trato de asegurarme de que estoy usando diseños sanos para una aplicación web impulsada por JSON.El mejor patrón para manejar bucles asíncronos en Node.js

Tengo un montón de datos almacenados en Redis y los estoy recuperando a través de un nodo, transmitiendo los resultados tal como vienen de Redis. He aquí un buen ejemplo de lo que estoy haciendo:

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     res.write("["); 
     replies.forEach(function (reply, i) { 
      rc.get(reply, function(err, reply) { 
       res.write(reply); 
       if (i == replies.length-1) { 
        res.write("]"); 
        res.end(); 
       } 
       else 
        res.write(","); 
      }); 
     }); 
    }); 
}); 

Esencialmente Estoy consiguiendo un conjunto de teclas de Redis y luego solicitar cada una, que fluye hacia fuera el resultado en JSON semi-manual creado (las cuerdas que salen de Redis ya está en JSON). Ahora esto funciona bien, pero no puedo evitar pensar que el i == replies.length-1 está un poco desordenado.

Podría hacer todo esto con mget en Redis, pero ese no es realmente el punto que trato de conseguir; es la mejor manera de manejar el bucle asincrónico con forEach, transmitir la salida y cerrar con gracia la conexión con res.end con el bucle terminado.

¿Es esta la mejor manera, o hay un patrón más elegante que podría seguir?

+0

de profundas devoluciones de llamada de funciones anidadas, lo usaría la asíncrono .js biblioteca. – BRampersad

Respuesta

6

El código anterior puede no hacer lo que esperas. Estás iniciando cada .get() en secuencia, pero es posible que no devuelvan la llamada en secuencia, por lo que los resultados podrían transmitirse en cualquier orden. Si desea transmitir los resultados en lugar de recopilarlos en la memoria, necesita .get() en secuencia.

Creo que caolan’s async library hace mucho más fácil. He aquí una manera que podría utilizar para obtener cada punto en la secuencia (de advertencia, no probado):

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     var i = 0; 
     res.write("["); 
     async.forEachSeries(replies, function(reply, callback){ 
      rc.get(reply, function(err, reply) { 
       if (err){ 
        callback(err); 
        return; 
       } 
       res.write(reply); 
       if (i < replies.length) { 
        res.write(","); 
       } 
       i++; 
       callback(); 
      }); 
     }, function(err){ 
      if (err) { 
       // Handle an error 
      } else { 
       res.end(']'); 
      } 
     }); 
    }); 
}); 

Si no se preocupan por el orden, sólo tiene que utilizar async.forEach() lugar.

Si no le importa recoger los resultados y quiere que vuelvan en secuencia, se puede usar async.map() como esto (advertencia, también no probado):

app.get("/facility", function(req, res) { 
    rc.keys("FACILITY*", function(err, replies) { 
     async.map(replies, rc.get.bind(rc), function(err, replies){ 
      if (err) { 
       // Handle an error 
      } else { 
       res.end('[' + replies.join(',') + ']'); 
      } 
     }); 
    }); 
}); 
+0

Eso es genial; gracias por el código; Probé la biblioteca asíncrona y funciona perfectamente. El orden no importa, pero la solución del mapa parece mucho más elegante, así que me puedo quedar con eso. –

+0

Estoy tratando de averiguar cómo funciona el parámetro rc.get.bind (rc) en la llamada a la función del mapa; es una forma ingeniosa de hacerlo. ¿Podrías explicarme un poco sobre cómo funciona exactamente? –

+0

@mjs [bind] (https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind) es parte de ECMAScript 5, y devuelve una copia de una función que está "vinculada" a un particular valor de 'this'. En este caso, significa que cuando async.map llama al 'get()', tendrá 'rc' como su valor' this'. – s4y

3

Usted puede usar la biblioteca async, proporciona algunos métodos prácticos para un bucle, como forEach:

forEach (arr, iterador, devolución de llamada)

aplica una función de iterador a cada elemento de una array, en paralelo. Se llama al iterador con un elemento de la lista y una devolución de llamada para cuando finaliza. Si el iterador pasa un error a esta devolución de llamada , la devolución de llamada principal para la función forEach es inmediatamente llamado con el error.

Tenga en cuenta que, dado que esta función aplica el iterador a cada elemento en paralelo, no hay garantía de que las funciones del iterador se completarán en en este orden.

Ejemplo

// assuming openFiles is an array of file names and saveFile is a function 
// to save the modified contents of that file: 

async.forEach(openFiles, saveFile, function(err){ 
    // if any of the saves produced an error, err would equal that error 
}); 
+0

Echaré un vistazo a esa biblioteca; gracias por el puntero. –

1

pero no puedo evitar pensar que el i == replies.length-1 es un poco desordenado?

He oído a mucha gente decir eso. Esta es la forma en que lo haría con la mano:

app.get("/facility", function(req, res, next) { 
    rc.keys("FACILITY*", function(err, replies) { 
    if (err) return next(err); 
    var pending = replies.length; 
    res.write("["); 
    replies.forEach(function (reply) { 
     rc.get(reply, function(err, reply) { 
     res.write(reply); 
     if (!--pending) { 
      res.write("]"); 
      return res.end(); 
     } 
     res.write(","); 
     }); 
    }); 
    }); 
}); 

Obviamente hacerlo a mano no es muy bonita, por lo que la gente ya lo tienen abstraído en una biblioteca o alguna otra función. Pero le guste o no, así es como se hace un bucle paralelo asíncrono. :)

Puede utilizar la biblioteca async mencionada anteriormente para ocultar las entrañas desagradables.

+0

Esa es una forma más ordenada de hacerlo; gracias por eso. –

Cuestiones relacionadas