2

Llevo un par de dias trabajando en este código.

Tengo dos consultas a mysql que se repiten con un forEach, y necesito esperar a que ambos forEach se completen si o si antes de seguir.

Explico mi codigo:

Una funcion que debe devolver un array con objetos, esta función recibe el id_user a buscar un 'cb' - que es una función callback que se encargara de manejar el array y mostrarlo en pantalla - .

En principio hago un select que me trae un array de ids que me servirán para la segunda consulta. Luego esta segunda consulta se repite en un bucle para traer un array de objetos de distintas tablas, los cuales 'desglozaré' en el segundo for para ir acumulándolos en mi array llamado 'matches'.

Mi objetivo es que el array 'matches' se llene de objetos que provienen de distintas tablas pero que tienen en común un id_user.


El problema es que la función forEach es asíncronica - lo que hace que se ejecute la linea cb(e,matches) antes de terminar los for, consiguiendo por pantalla un array vació.

Estube leyendo sobre Promesas, pero no hay caso, no logro entenderlas, los ejemplos de internet son muy básicos y no logro encajar este concepto en mi código.

¿Alguien conoce cómo puedo solucionar esta situación?

Este es mi código:

var getMatchByUser=(id_user,cb)=>{
    let matches=[];

    db.query(`SELECT id_community FROM members_community WHERE id_user=${id_user}`,[],(e,d)=>{
       d.forEach(m=>{
          db.query(`SELECT * FROM matches_${m.id} WHERE created_by=${id_user}`,[],(e1,d1)=>{
            d1.forEach(m1 => {
                matches.push(m1);
            });
          });
       });
       cb(e,matches);
    });
}
Mauricio Arias Olave
  • 3,098
  • 6
  • 26
  • 55
Crystal
  • 61
  • 1
  • 4
  • 2
    Saludos. ¿Hay posibilidad de que ambas consultas estén unificadas - *(es decir, que no tenga la necesidad de usar dos select separados)*?. La verdad no me es claro por qué estás haciendo dos consultas... Puedes mirar esta [respuesta](https://es.stackoverflow.com/a/1544/78) acerca de cómo obtener los resultados de una llamada asíncrona. – Mauricio Arias Olave Jan 28 '19 at 21:12
  • Qué devuelve `db.query()`? Dónde puedo ver la documentación de ese método? – kosmosan Jan 29 '19 at 08:50
  • Hola buenas, puedes usar `Promise()`. Lo que hace es esperar que se ejecuten funciones necesarias y despues hacer lo que quieres. Resumido, te obliga a esperar que las funciones asincronas se completen una a una y solo DESPUES hacer lo que tu quieres. Aqui tienes la documentación https://developers.google.com/web/fundamentals/primers/promises?hl=es – Ivan Isayenko Jan 29 '19 at 09:09
  • @MauricioAriasOlave, no, no tengo manera, ya que las tablas con nombre `matches_{numero}` pueden ser cientos o miles, mientras que el array de la primera consulta tiene un máximo de 20 registros, de esta manera hago una especie de "filtro". Necesito que la segunda query se repita N cantidad de veces dependiendo la cantidad de registros obtenidos en la primera query. Y ya una vez terminado todo ejecutar la `cb(e,matches)` enviando el array `matches` con los objetos obtenidos de todos lados – Crystal Jan 29 '19 at 14:34
  • @Crystal si echas un vistazo a mi respuesta, ahí tienes la solución. – kosmosan Jan 29 '19 at 15:32

1 Answers1

1

El método db.query() es el asíncrono pero no sé si devuelve una promesa o no. Actualmente lo que sucede es lo siguiente:

// query 1. este es un método asíncrono
db.query(`SELECT id_community FROM members_community WHERE id_user=${id_user}`,[],(e,d)=>{

    // en su callback, recorremos uno de los objetos de respuesta
    d.forEach(m => {

        // query 2. estas llamadas son asíncronas, por lo que se lanzan pero continúa la ejecución
        db.query(`SELECT * FROM matches_${m.id} WHERE created_by=${id_user}`,[],(e1,d1)=>{
            d1.forEach(m1 => {
                matches.push(m1);
            });
        });
    });

    // esto se lanza antes de finalizar query 2
    cb(e,matches);
});

Lo que podemos hacer en este caso para tener un control sobre los métodos asíncronos, es crear nuestras propias promesas:

// convertimos el método en async para poder utilizar awaits
const getMatchByUser = async(id_user, cb) => {
    const matches = [];

    // creamos un método usable para obtener las consultas de la bd (este método podría/debería estar fuera de este scope)
    const doQuery = query => new Promise(resolve => db.query(query, [], (e, d) => resolve([e, d]));

    // recogemos la primera consulta
    const [a, b] = await doQuery(`SELECT id_community FROM members_community WHERE id_user=${id_user}`);

    // recorremos los resultados
    b.forEach(m => {

        // recogemos la segunda consulta
        const [c, d] = await doQuery(`SELECT * FROM matches_${m.id} WHERE created_by=${id_user}`);

        // recorremos los resultados
        d.forEach(ml => matches.push(ml));
    });

    // utilizamos los resultados
    cb(a, matches);
};
kosmosan
  • 538
  • 2
  • 5
  • si, gracias, es masomenos lo que estoy tratando de conseguir. si bien este código no funciona me da una idea de por donde encarar. En este codigo nunca entra el bucle `b.forEach`, salta de una al `cb` con el mismo resultado – Crystal Jan 29 '19 at 15:40
  • Está hecho al vuelo, sin probar, pero a excepción de algún typo que pueda haber debería funcionar. Cualquier cosa por aquí andamos – kosmosan Jan 29 '19 at 15:42
  • Hmm le has puesto la declaración `async` a `getMatchByUser()`? Puedo equivocarme pero a priori un copy&paste debería funcionar. – kosmosan Jan 29 '19 at 15:44
  • eso es indistinto ya que tiene su propio callback para manejar lo que suceda. en tu código me exige por sintaxis que la linea `const[c,d]=await doQuery...` este dentro de una función async, lo que desacredita y deja inutilizable el `forEach`. este forEach es el que es asincronico, no se detiene a completar el bucle, sigue a la siguiente linea del `cb` – Crystal Jan 29 '19 at 15:49
  • El `forEach` es asíncrono? Lo leí en tu pregunta pero di por hecho que era un error y que era `db.query()` el asíncrono, de ahí que tuviera un callback. Le doy una vuelta al código y edito la respuesta cuando lo tenga, a ver si lo sacamos adelante. – kosmosan Jan 29 '19 at 16:00
  • Ese `forEach` pertenece a nodejs? – kosmosan Jan 29 '19 at 16:12
  • si bien estoy utilizando nodejs, `forEach` es nativo de cualquier array de el mismo javascript .___. – Crystal Jan 29 '19 at 16:23
  • Sí, pero no es asíncrono. En cambio, si no me equivoco, en nodejs se puede utilizar una librería `async` la cual tiene un método `forEach()`. La pregunta ahora, antes de hacer nada, es: el `forEach()` que estás utilizando es el nativo de js (síncrono) o es asíncrono? Qué tipo de objeto devuelve `db.query()` en su callback? – kosmosan Jan 29 '19 at 16:25
  • el clásico `Array.prototype.forEach()` que debería ser sincrono. Igual estoy intentando convertir cada bucle en promesa y obligarlas a esperar, no es lo mejor pero debería funcionar – Crystal Jan 29 '19 at 16:32
  • Entonces el `forEach()` no es asíncrono en absoluto! No sé si llegaste a hacerlo, pero, si copias el código que puse en la respuesta al completo (con su declaración async en la función), no funciona? Porque veamos, si lo que devuelve `db.query()` es un simple array, ese snippet debería funcionar tal cual está. Antes comentaste que al utilizar la declaración `async` dejaba inutilizable los `forEach()` pero por qué motivo? – kosmosan Jan 29 '19 at 16:38