Node.js: ¿Cómo manejas las devoluciones de llamada en un bucle?

Estoy usando Node.js y Box SDK. Mi código (¡fallado!) Se ve así:

var connection = box.getConnection(req.user.login); connection.ready(function () { connection.getFolderItems(0, null, function (err, result) { if (err) { opts.body = err; } else { opts.body = result; var a = []; for (var i=0; i < result.entries.length; i++) { connection.getFileInfo(result.entries[i].id, function (err, fileInfo) { if (err) { opts.body = err; } else { a.push(fileInfo); } });} } 

En términos “de procedimiento”, esto es lo que estoy tratando de hacer:

 var connection= box.getConnection() var items = connection.getFolderItems() var itemList = new List foreach (item in items) { connection.getFileInfo(item.id) itemList.add(item) } display(itemList) 

Mi problema es que connection.getFolderItems() y connection.getFileInfo() son asíncronos: el bucle “for” sale antes de que se devuelvan todos los resultados.

P: ¿Cuál es la mejor manera en Node.js para 1) obtener el resultado de la primera llamada asíncrona, 2) iterar a través de la lista, hacer más llamadas asíncronas, y 3) procesar los resultados cuando todo está “hecho”.

P: ¿Las promesas son una buena opción aquí?

P: ¿Se hace () / siguiente () una opción?

P: ¿Hay algún “lenguaje estándar” en Node.js para este tipo de escenario?

Las promesas son una gran idea, pero es posible que desee echar un vistazo al módulo asíncrono, específicamente a los controladores de colección. Te permite ejecutar llamadas asíncronas contra una lista de “cosas” y te da un lugar para ejecutar un método cuando se realizan todas las llamadas asíncronas. No sé si esto es mejor que las promesas, pero las opciones siempre son buenas.

 // Should give you a rough idea async.each(items, function (item, callback) { connection.getFileInfo(result, callback); }, function (err) { console.log('All done'); }); 

https://github.com/caolan/async#each

P: ¿Cuál es la mejor manera en Node.js para 1) obtener el resultado de la primera llamada asíncrona, 2) iterar a través de la lista, hacer más llamadas asíncronas, y 3) procesar los resultados cuando todo está “hecho”.

Hay múltiples enfoques. Codificación manual, promesas, biblioteca asíncrona. “Lo mejor” está en el ojo del espectador, así que no es realmente lo que debemos decir aquí. Yo uso Promesas para todos mis códigos asíncronos. Se han estandarizado oficialmente en ES6 y existen implementaciones sólidas y sólidas (me gusta Bluebird por las características adicionales que tiene más allá del estándar que simplifica las tareas asíncronas complejas y por su característica promisifyAll() que le brinda una interfaz prometedora en cualquier operación asíncrona estándar que utiliza la convención de llamada async callback).

Aconsejaría no codificar a mano las operaciones asíncronas complicadas porque el manejo robusto de errores es muy difícil y se pueden comer excepciones en silencio dentro de las devoluciones de llamadas asíncronas que conducen a la gestión de errores perdidos y la depuración difícil. La biblioteca Async es probablemente la mejor forma no Prometida de hacer las cosas, ya que proporciona algunas características de infraestructura y sincronización en torno a las devoluciones de llamadas asíncronas.

Yo personalmente prefiero las promesas. Creo que veremos más API asíncronas estandarizadas al devolver una promesa a medida que avanza el tiempo, así que creo que es una mejor opción para una forma de aprender y progtwigr con visión de futuro.

P: ¿Las promesas son una buena opción aquí?

Sí, las promesas te permiten ejecutar un montón de operaciones asíncronas y luego usar algo como Promise.all() para saber cuándo están Promise.all() . También recostackrá todos los resultados de todas las operaciones asíncronas para usted.

P: ¿Se hace () / siguiente () una opción?

No estoy exactamente seguro de lo que está preguntando aquí, pero puede codificar manualmente las operaciones asíncronas para que se ejecuten en paralelo y saber cuándo se realizan o para ejecutarlas de forma secuencial y cuándo se realizan. Las promesas hacen mucho más de este trabajo por ti, pero puedes hacerlo manualmente sin ellas.

P: ¿Hay algún “lenguaje estándar” en Node.js para este tipo de escenario?

Si se usan promesas, habría una forma común de hacer esto. Si no usa promesas, probablemente no haya un “lenguaje estándar” ya que hay muchas formas diferentes de codificarlo usted mismo.

Implementación de la promesa

Aquí hay un ejemplo que usa la biblioteca Bluebird Promise en node.js:

 var Promise = require('bluebird'); var connection = Promise.promisifyAll(box.getConnection(req.user.login)); connection.ready(function() { connection.getFolderItemsAsync(0, null).then(function(result) { return Promise.map(result.entries, function(item) { return connection.getFileInfoAsync(item.id); }) }).then(function(results) { // array of results here }, function(err) { // error here }); }); 

Así es como funciona esto:

  1. Promise el objeto de conexión para que todos sus métodos tengan una versión que devuelva una promesa (solo agregue “Async” al final del método para llamar a esta versión promisificada).

  2. Llame a getFolderItemsAsync() y su promesa se resolverá con la matriz result.entries

  3. Ejecute un mapa de esa matriz, ejecute todas las operaciones en paralelo y devuelva una promesa que se resuelva con una matriz de resultados ordenados cuando se realicen todas las operaciones.

  4. El resultado real para cada entrada se logra con connection.getFileInfoAsync() .

  5. Crear un controlador de resolución y un controlador de rechazo. Si se produce algún error en cualquier parte del proceso, se propagará hasta el controlador de rechazo. Si todas las operaciones son exitosas, se llamará al último controlador de resolución con una matriz ordenada de resultados.

La versión anterior se cancela si hay un error y no obtiene más resultados que el código de error. Si desea continuar con un resultado null si hay un error, puede usar algo como esto:

 var Promise = require('bluebird'); var connection = Promise.promisifyAll(box.getConnection(req.user.login)); connection.ready(function() { connection.getFolderItemsAsync(0, null).then(function(result) { return Promise.map(result.entries, function(item) { return connection.getFileInfoAsync(item.id).catch(function(err){ // post the results as null and continue with the other results return null; }); }) }).then(function(results) { // array of results here (some might be null if they had an error) }, function(err) { // error here }); }); 

Versión codificada manualmente

Aquí hay una versión codificada manualmente. La clave para esto es detectar cuándo se realiza el bucle asíncrono comparando if (results.length === result.entries.length) . Nota: Esto tiene un manejo de errores incompleto que es una de las dificultades de codificar esto a mano y no usar un marco asíncrono como promesas.

 var connection = box.getConnection(req.user.login); connection.ready(function () { connection.getFolderItems(0, null, function (err, result) { if (err) { // do error handling here opts.body = err; } else { var results = []; for (var i = 0; i < result.entries.length; i++) { connection.getFileInfo(result.entries[i].id, function (err, fileInfo) { if (err) { // do error handling here opts.body = err; results.push(null); } else { results.push(fileInfo); } // if done with all requests if (results.length === result.entries.length) { // done with everything, results are in results // process final results here } }); } } }); }); 

P: ¿Cuál es la mejor manera en Node.js para 1) obtener el resultado de la primera llamada asíncrona, 2) iterar a través de la lista, hacer más llamadas asíncronas, y 3) procesar los resultados cuando todo está “hecho”.

Puede usar una biblioteca asíncrona o prometer las llamadas de función y usar Promesa en su lugar. Ambos son fáciles de usar.

P: ¿Las promesas son una buena opción aquí?

Sí. Pero requiere que prometas tu método antes de usarlo.

P: ¿Se hace () / siguiente () una opción?

Por lo que entiendo, es un concepto completamente diferente. Lo done aquí se refiere a una función de callback a la que puede llamar una vez que finaliza el método. Y next usualmente se usa en modo expreso para pasar una solicitud a la siguiente ruta, lo que creo que no es relevante para la pregunta que está haciendo.

P: ¿Hay algún “lenguaje estándar” en Node.js para este tipo de escenario?

Por lo general, se hace referencia a llamadas simplemente “asíncronas” o “no bloqueantes”