Estoy tratando de descubrir cómo configurar correctamente este bloque de código para que la función de confirmación espere hasta que se inserten todas las filas. Actualmente estoy leyendo un csv y necesito insertar una nueva fila por columna en una tabla. También necesito agregar una fila en una tabla principal para eso. Necesito todo eso para terminar antes de llamar a commit. Todavía tengo que dominar las devoluciones de llamada, así que sé gentil.

db.beginTransaction(function (err) {
    if (err) 
    {
        //could not begin a transaction for some reason.
        logger.error("beginTransaction error: " +err);
    }
    //need to wrap this *************
    db.query("INSERT INTO TABLE VALUES ('" + unique_id + "','real_time','" + msg + "',1,1,LOCALTIMESTAMP)", function(err){
        if(err)
        {
            logger.error("error insert into parent table: "+err);
        }
    });

    for(var i = 0; i < headers.length; i++)
    {
        //replaces single quote (') with two single quotes to escape it ('')
        values[i] = values[i].replace("'","''");
        db.query("INSERT INTO TABLE VALUES ('" + unique_id + "','" + headers[i] + "',0,'" + values[i] + "')", function(err){
            if(err)
            {
                logger.error("error insert into child table: "+err);
            }
        });
    }
    //To here ************
    db.commitTransaction(function (err) {
        if (err) 
        {
            //error during commit
            logger.error("Commit error: "+err);
        }
    }); //end of commitTransaction
    callback();
});//End of beginTransaction
2
Crash667 12 ene. 2018 a las 21:42

3 respuestas

La mejor respuesta

Hay tres formas básicas de abordar este problema de sincronización que demostraré aquí usando el nuevo estilo funciones de flecha. La forma tradicional del nodo es con devoluciones de llamada:

a((err, resultA) => {
  // Fires when A is done or errored out

  if (err) {
    // Log, panic, etc.
    return;
  }

  b((err, resultB) => {
    // Fires when A and B are done or A is done and B errored out

    if (err) {
      // Log, panic, etc.
      return;
    }

    c((err, resultC) => {
     // Fires when A, B and C are done or A and B are done and C errored out

      if (err) {
        // Log, panic, etc.
        return;
      }
    });
  });
});

Esto es lo que la gente llama "infierno de devolución de llamada" porque el código de propagación de errores y anidamiento se vuelve cada vez más ridículo a medida que sus dependencias crecen en complejidad. Este estilo me parece insostenible para cualquier aplicación no trivial.

El siguiente estilo está basado en promesas:

a().then(resultA => {
  // Fires when A is done
  return b();
}).then(resultB => {
  // Fires when B is done
  return c();
}).then(resultC => {
  // Fires when C is done
}).catch(err => {
  // Fires if any of the previous calls produce an error
});

Esto tiende a ser mucho más "plano" y más fácil de seguir, pero sigue siendo una gran sintaxis para lo que debería ser simple. El nuevo estilo asíncrono / espera se basa en promesas al agregar soporte para ellos a la sintaxis de JavaScript:

try {
  let resultA = await a();
  let resultB = await a();
  let resultC = await a();
} catch(err) {
  // Fires when any error occurs
}

Esto funciona dentro de cualquier función etiquetada async como:

async function runQueries() {
  // async code
}

Lo que puede hacer tu vida mucho más fácil. También puede usar la notación convencional try / catch para errores, se propagan en consecuencia.

2
tadman 12 ene. 2018 a las 19:02

Realmente no entiendo su código, ¿por qué no devuelve o detiene su código si hay un error?

Aquí, por ejemplo, debe tener su código como este

db.beginTransaction(function (err) {
    if (err) 
        return logger.error("beginTransaction error: " +err), db.rollback(/*...*/)
    //....
}

En segundo lugar, debe cambiar todo el código con la sintaxis async/await.

async function foo () {
    await new Promise((next, err)=> {
        db.beginTransaction(e => e ? err(e) : next())
    })

    await new Promise((next, err)=> {
        db.query(`INSERT INTO TABLE VALUES ('${unique_id}','real_time','${msg}',1,1,LOCALTIMESTAMP)`, e => e ? err(e) : next())
    })

    for (var i = 0; i < headers.length; i++) {
        await new Promise((next, err)=> {
            db.query(`INSERT INTO TABLE VALUES ('${unique_id}','${headers[i]}',0,'${values[i]}')`, e => e ? err(e) : next())
        })
    }

    await new Promise((next, err)=> {
        db.commitTransaction(e => e ? err(e) : next())
    })
}

foo()
.then(()=> callback())
.catch(e=> logger.error(`Error: ${e}`))
0
Fernando Carvajal 24 ene. 2018 a las 15:22

Como dice tadman, es muy importante que no escapes manualmente de los valores y que utilices consultas parametrizadas. Asegúrate de arreglar esto primero.

Lamentablemente, no parece que node-odbc respalde las promesas. Es posible que pueda hacer que funcione con algo como Bluebird.promisify. Actualmente, lo que desea es realizar un seguimiento de cuántas inserciones exitosas se completaron y luego confirmar la transacción si todas se completan con éxito.

let successfulInsertions = 0;
let insertionAttempts = 0;
for(var i = 0; i < headers.length; i++) {
    db.query("INSERT INTO TABLE VALUES (?, ?, ?, ?)", params, err => {
        if (err) {
            logger.error("error insert into child table: "+err);
        }
        else {
          successfulInsertions++;
        }
        insertionAttempts++;

        if (insertionAttempts === headers.length) {
            if (successfulInsertions === insertionAttempts) {
                db.commitTransaction();
            }
            else {
                db.rollbackTransaction();
            }
            callback();
        }
    });
}

Hay bibliotecas por ahí que ayudarán con esto, pero requerirían reescribir un poco su código. Si puede usar promisifyAll de Bluebird en la biblioteca node-odbc, lo volvería a escribir. usando async / await (esto sigue siendo asíncrono y funciona igual):

await db.beginTransactionAsync();
try {
  await db.queryAsync("INSERT INTO TABLE VALUES (?, ?, ?, ?)", params);

  await Promise.all(headers.map((header, i) =>
    db.queryAsync("INSERT INTO TABLE VALUES (?, ?, ?, ?)", [unique_id, header, 0, values[i])
  );
  await db.commitTransactionAsync();
} catch (err) {
  logger.error(err);
  db.rollbackTransaction();
}

Tenga en cuenta que si beginTransaction arroja un error por alguna razón, este código arrojará un error.

1
Explosion Pills 12 ene. 2018 a las 18:59