Estoy ocupado trabajando en un punto final para un sistema de informes. El nodo asincrónico me está dando problemas, aunque prefiero no forzarlo para que sea sincrónico.
Estamos usando MongoDB y Mongoose. Tengo que consultar expresiones regulares sobre la colección A, luego, para cada documento que se devuelve, consultar varios documentos contenidos para completar un objeto/matriz JSON que se devolverá.
Puedo usar populate para la mayoría de los datos, excepto las consultas finales en bucle, que es donde se activa el asíncrono y devuelve mi informe temprano. ¿Hay una manera elegante de hacer esto? ¿O debería dividirme en una función diferente y llamarla varias veces para apegarme a las functions should do only one thing regla?
Código de ejemplo:
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) { var report = []; A.map(function(a)){ report[a.name] = []; D.aggregate([ { $match: { id: B._id } }, { $group: { _id: null, count: { $sum: 1 } } } ], function(err, result) { C.map(function(c){ report[a.name].push({ 'field1': c.field1, 'field2': c.field2, 'field3': c.field3, 'count': result.count }); }); }); } return report; });El problema aquí es con la lógica/async. No con la sintaxis, de ahí el código semi-pseudo.
Cualquier ayuda o consejo sería muy apreciado.
Debe familiarizarse con las promesas y con async en general. Debido a que está devolviendo una matriz, ese es el valor que obtendrá.
Tiene algunas opciones cuando se trata de Async, pero en su caso, desea buscar dos soluciones:
// callbacks getSetOfIDs((err, ids) => { let remaining = ids.length; let things = []; let failed = false; ids.forEach(id => { getThingByID(id, (err, thing) => { if (failed) { return; } if (err) { failed = true; handleFailure(err); } else { remaining -= 1; things.push(thing); if (!remaining) { handleSuccess(things); } } }); }); }); Tenga en cuenta que no estoy devolviendo things , lo estoy pasando a una devolución de llamada.
Puede usar funciones de orden superior para limpiar este tipo de cosas.
// cleaned up callbacks function handleNodeCallback (succeed, fail) { return function (err, data) { if (err) { fail(err); } else { succeed(data); } }; } function handleAggregateCallback (succeed, fail, count) { let items = []; let failed = false; const ifNotFailed = cb => data => { if (!failed) { cb(data); } }; const handleSuccess = ifNotFailed((item) => { items.push(item); if (items.length === count) { succeed(items); } }); const handleFailure = ifNotFailed((err) => { failed = true; fail(err); }); return handleNodeCallback(handleSuccess, handleFailure); }Un poco de código de ayuda más tarde, y estamos listos para comenzar:
// refactored callback app code (note that it's much less scary) getSetOfIDs((err, ids) => { const succeed = (things) => app.display(things); const fail = err => app.apologize(err); if (err) { return fail(err); } let onThingResponse = handleAggregateCallback(succeed, fail, ids.length); ids.forEach(id => getThingByID(id, onThingResponse)); });Tenga en cuenta que, aparte de las funciones de orden superior, nunca devuelvo nada, siempre paso continuaciones (cosas que hacer a continuación, con un valor).
El otro método es Promesas.
// Promises getSetOfIDs() .then(ids => Promise.all(ids.map(getThingByID))) .then(things => app.display(things)) .catch(err => app.apologize(err)); Para comprender realmente lo que sucede aquí, aprenda Promises, el método estático Promise.all y array.map() .
Ambos conjuntos de código teóricamente hacen exactamente lo mismo, excepto que en este último caso getSetOfIDs y getThingByID no aceptan devoluciones de llamada, sino que devuelven promesas.
generalmente en llamadas asíncronas, después de la declaración de devolución, se cancelan las operaciones.
tal vez pueda devolver el objeto de informe solo cuando todo esté hecho y bien.
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) { var report = []; A.map(function(a)){ report[a.name] = D.aggregate([ { $match: { id: B._id } }, { $group: { _id: null, count: { $sum: 1 } } } ], function(err, result) { if(err){ return []; } var fields = [] C.map(function(c){ fields.push({ 'field1': c.field1, 'field2': c.field2, 'field3': c.field3, 'count': result.count }); }); return fields; }); } return report; });Solo usa promesas:
A.find({ name: regex }).populate({ path: 'B', populate: { path: 'B.C', model: 'C' } }).exec(function(err, A) { var report = []; return Promise.all([ A.map(function(a)){ return new Promise(function(resolve, reject) { report[a.name] = []; D.aggregate([{ $match: { id: B._id }},{$group: {_id: null,count: { $sum: 1 }}}], function(err, result) { if(err) { reject(err) } else { C.map(function(c){ report[a.name].push({ 'field1': c.field1, 'field2': c.field2, 'field3': c.field3, 'count': result.count }); }); resolve(report) } }); } })]) }) .then(function(report){ console.log(report) }) .catch(function(err){ console.log(err) })