Consultas de bases de datos sincrónicas con Node.js

Tengo una aplicación Node.js / Express que consulta una base de datos MySQL dentro de la ruta y muestra el resultado al usuario. Mi problema es cómo puedo ejecutar las consultas y bloquear hasta que ambas consultas se realicen antes de redireccionar al usuario a la página que solicitaron.

En mi ejemplo, tengo 2 consultas que deben finalizar antes de renderizar la página. Puedo hacer que las consultas se ejecuten de forma sincrónica si incluyo la consulta 2 dentro de la callback de ‘resultado’ de la consulta 1. Sin embargo, esto se volverá muy intrincado cuando aumente el número de consultas.

¿Cómo hago para ejecutar varias consultas de bases de datos (en este caso 2) de forma síncrona sin anidar la consulta posterior en la callback ‘resultado’ de la consulta anterior?

He visto los “goodies de Control de flujo / Asincronización” en los módulos de Nodo y probé flow-js pero no puedo hacer que funcione con las consultas asincrónicas.

A continuación se enumeran las 2 consultas que estoy intentando ejecutar desde la ruta ‘/ home’. ¿Pueden los expertos en Nodo explicar la forma “correcta” de hacer esto?

app.get('/home', function (req,res) { var user_array = []; var title_array = []; // first query var sql = 'select user_name from users'; db.execute(sql) .addListener('row', function(r) { user_array.push( { user_name: r.user_name } ); }) .addListener('result', function(r) { req.session.user_array = user_array; }); // second query var sql = 'select title from code_samples'; db.execute(sql) .addListener('row', function(r) { title_array.push( { title: r.title } ); }) .addListener('result', function(r) { req.session.title_array = title_array; }); // because the queries are async no data is returned to the user res.render('home.ejs', {layout: false, locals: { user_name: user_array, title: title_array }}); }); 

El objective con el nodo no es preocuparse por el orden en que ocurren las cosas. Esto puede complicar algunos escenarios. No hay vergüenza en anular las devoluciones de llamada. Una vez que esté acostumbrado a cómo se ve, es posible que encuentre que realmente prefiere ese estilo. Hago; está muy claro qué orden de callbacks se disparará. Puede renunciar a las funciones anónimas para hacerlo menos detallado si es necesario.

Si está dispuesto a reestructurar un poco su código, puede usar el método de callback nested “típico”. Si desea evitar devoluciones de llamada, existen numerosos marcos asíncronos que intentarán ayudarlo a hacer esto. Uno que quizás desee verificar es async.js (https://github.com/fjakobs/async.js). Ejemplo de cada uno:

 app.get('/home', function (req,res) { var lock = 2; var result = {}; result.user_array = []; result.title_array = []; var finishRequest = function(result) { req.session.title_array = result.title_array; req.session.user_array = result.user_array; res.render('home.ejs', {layout: false, locals: { user_name: result.user_array, title: result.title_array }}); }; // first query var q1 = function(fn) { var sql = 'select user_name from users'; db.execute(sql) .addListener('row', function(r) { result.user_array.push( { user_name: r.user_name } ); }) .addListener('result', function(r) { return fn && fn(null, result); }); }; // second query var q2 = function(fn) { var sql = 'select title from code_samples'; db.execute(sql) .addListener('row', function(r) { result.title_array.push( { title: r.title } ); }) .addListener('result', function(r) { return fn && fn(null, result); }); } //Standard nested callbacks q1(function (err, result) { if (err) { return; //do something} q2(function (err, result) { if (err) { return; //do something} finishRequest(result); }); }); //Using async.js async.list([ q1, q2, ]).call().end(function(err, result) { finishRequest(result); }); }); 

Para una sola vez, probablemente solo use un enfoque de tipo de conteo de referencia. Simplemente realice un seguimiento de la cantidad de consultas que desea ejecutar y envíe la respuesta cuando todas hayan finalizado.

 app.get('/home', function (req,res) { var lock = 2; var user_array = []; var title_array = []; var finishRequest = function() { res.render('home.ejs', {layout: false, locals: { user_name: user_array, title: title_array }}); } // first query var sql = 'select user_name from users'; db.execute(sql) .addListener('row', function(r) { user_array.push( { user_name: r.user_name } ); }) .addListener('result', function(r) { req.session.user_array = user_array; lock -= 1; if (lock === 0) { finishRequest(); } }); // second query var sql = 'select title from code_samples'; db.execute(sql) .addListener('row', function(r) { title_array.push( { title: r.title } ); }) .addListener('result', function(r) { req.session.title_array = title_array; lock -= 1; if (lock === 0) { finishRequest(); } }); }); 

Un enfoque incluso más agradable sería simplemente llamar a finishRequest () en cada callback de ‘resultado’ y verificar las matrices no vacías antes de procesar la respuesta. Si eso funcionará en su caso depende de sus requisitos.

Aquí hay un truco realmente fácil para manejar múltiples devoluciones de llamadas.

 var after = function _after(count, f) { var c = 0, results = []; return function _callback() { switch (arguments.length) { case 0: results.push(null); break; case 1: results.push(arguments[0]); break; default: results.push(Array.prototype.slice.call(arguments)); break; } if (++c === count) { f.apply(this, results); } }; }; 

Ejemplo

Uso:

 var handleDatabase = after(2, function (res1, res2) { res.render('home.ejs', { locals: { r1: res1, r2: res2 }): }) db.execute(sql1).on('result', handleDatabase); db.execute(sql2).on('result', handleDatabase); 

Así que, básicamente, necesita contar de referencia. Este es el enfoque estándar en estas situaciones. De hecho, uso esta pequeña función de utilidad en lugar de control de flujo.

Si quieres una solución de control de flujo completa, recomendaría futuros JS

Encuentro que la biblioteca asíncrona es la mejor para cosas como esta. https://github.com/caolan/async#parallel

No puedo probar esto ni nada, así que perdónenme si hay algunos errores tipográficos. Refactoré su función de consulta para que sea reutilizable. Por lo tanto, llamar a queryRows devolverá una función que coincida con el formato de las funciones de callback paralelas del módulo asíncrono. Después de que se completen ambas consultas, llamará a la última función y pasará el resultado de las dos consultas como un argumento, que puede leer para pasar a su plantilla.

 function queryRows(col, table) { return function(cb) { var rows = []; db.execute('SELECT ' + col + ' FROM ' + table) .on('row', function(r) { rows.push(r) }) .on('result', function() { cb(rows); }); } } app.get('/home', function(req, res) { async.parallel({ users: queryRow('user_name', 'users'), titles: queryRow('title', 'code_samples') }, function(result) { res.render('home.ejs', { layout: false, locals: {user_name: result.users, title: result.titles} }); }); }); 

Aquí hay algunas soluciones, pero en mi opinión la mejor solución es hacer que el código sea sincrónico de una manera muy fácil.

Puede usar el paquete “sincronizar”.

Sólo

npm instalar sincronizar

Luego var sync = require(synchronize);

Ponga lógica que debe ser sincrónica en una fibra mediante el uso de

sync.fiber(function() { //put your logic here }

Un ejemplo para dos consultas mysql:

 var express = require('express'); var bodyParser = require('body-parser'); var mysql = require('mysql'); var sync = require('synchronize'); var db = mysql.createConnection({ host : 'localhost', user : 'user', password : 'password', database : 'database' }); db.connect(function(err) { if (err) { console.error('error connecting: ' + err.stack); return; } }); function saveSomething() { var post = {id: newId}; //no callback here; the result is in "query" var query = sync.await(db.query('INSERT INTO mainTable SET ?', post, sync.defer())); var newId = query.insertId; post = {foreignKey: newId}; //this query can be async, because it doesn't matter in this case db.query('INSERT INTO subTable SET ?', post, function(err, result) { if (err) throw err; }); } 

Cuando se llama a “saveSomething ()”, inserta una fila en una tabla principal y recibe la última identificación insertada. Después de eso, se ejecutará el siguiente código. No hay necesidad de anidar promesas o cosas por el estilo.

Opción uno: si todas sus consultas se relacionan entre sí, cree un procedimiento almacenado, ponga toda su lógica de datos en él y tenga un solo db.execute

Opción dos: si su db usa una conexión, entonces ordena que se ejecute en serie y que puede usar esto como ayudante asincrónico.

 db.execute(sql1).on('row', function(r) { req.session.user_array.push(r.user); }); db.execute(sql2) .on('row', function(r) { req.session.title_array.push(r.title); }) .on('end'), function() { // render data from req.session }); 

Puede usar fibras para escribir código pseudo-sincrónico con Node.JS eche un vistazo a estas pruebas para DB https://github.com/alexeypetrushin/mongo-lite/blob/master/test/collection.coffee que son asincrónicas, pero se ve como sincrónico, más detalles http://alexeypetrushin.github.com/synchronize