web-dev-qa-db-fra.com

Requêtes de base de données synchrones avec Node.js

J'ai une application Node.js/Express qui interroge une base de données MySQL dans l'itinéraire et affiche le résultat à l'utilisateur. Mon problème est de savoir comment exécuter les requêtes et bloquer jusqu'à ce que les deux requêtes soient terminées avant de rediriger l'utilisateur vers la page demandée?

Dans mon exemple, j'ai 2 requêtes qui doivent se terminer avant de rendre la page. Je peux faire en sorte que les requêtes s'exécutent de manière synchrone si j'imbrique la requête 2 dans le rappel "résultat" de la requête 1. Cela deviendra cependant très compliqué lorsque le nombre de requêtes augmentera.

Comment puis-je exécuter plusieurs requêtes de base de données (dans ce cas 2) de manière synchrone sans imbriquer la requête suivante dans le rappel de "résultat" de la requête précédente?

J'ai regardé les "goodies de contrôle de flux/asynchrone" dans les modules Node et j'ai essayé flow-js mais je ne peux pas le faire fonctionner avec les requêtes asynchrones.

Voici les 2 requêtes que j'essaie d'exécuter à partir de la route '/ home'. Les experts Node peuvent-ils expliquer la "bonne" façon de procéder?.

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 }});
});
52
Rick

L'objectif avec le nœud n'est pas de se soucier de l'ordre dans lequel les choses se produisent. Cela peut compliquer certains scénarios. Il n'y a aucune honte à imbriquer des rappels. Une fois que vous êtes habitué à son apparence, vous pouvez constater que vous préférez réellement ce style. Je fais; il est très clair quel ordre les rappels se déclencheront. Vous pouvez renoncer aux fonctions anonymes pour le rendre moins verbeux si vous le devez.

Si vous souhaitez restructurer un peu votre code, vous pouvez utiliser la méthode de rappel imbriquée "typique". Si vous souhaitez éviter les rappels, il existe de nombreux frameworks asynchrones qui vous aideront à le faire. Un que vous voudrez peut-être vérifier est async.js (https://github.com/fjakobs/async.js). Exemple de chacun:

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);
    });

});

Pour une seule fois, j'utiliserais probablement une approche de type comptage de référence. Gardez simplement une trace du nombre de requêtes que vous souhaitez exécuter et restituez la réponse une fois toutes terminées.

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();
            }
        });
});

Une approche encore plus agréable serait d'appeler simplement finishRequest () dans chaque rappel de "résultat" pour vérifier les tableaux non vides avant de rendre la réponse. Que cela fonctionne dans votre cas dépend de vos besoins.

54
jslatts

Voici une astuce très simple pour gérer plusieurs rappels.

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);
    }
  };
};

Exemple

Utilisation:

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);

Donc, fondamentalement, vous devez compter les références. Il s'agit de l'approche standard dans ces situations. J'utilise en fait cette petite fonction utilitaire au lieu du contrôle de flux.

Si vous voulez une solution complète de contrôle de flux, je recommanderais futuresJS

17
Raynos

Je trouve que la bibliothèque asynchrone est la meilleure pour des choses comme ça. https://github.com/caolan/async#parallel

Je ne peux pas tester ceci ou quoi que ce soit, alors pardonnez-moi s'il y a des fautes de frappe. J'ai refactorisé votre fonction de requête pour qu'elle soit réutilisable. Ainsi, l'appel de queryRows retournera une fonction qui correspond au format des fonctions de rappel parallèle du module asynchrone. Une fois les deux requêtes terminées, il appellera la dernière fonction et transmettra le résultat des deux requêtes en tant qu'argument, que vous pourrez lire pour passer à votre modèle.

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} 
    });
  });
});
14
loganfsmyth

Il y a quelques solutions ici, mais à mon avis, la meilleure solution est de rendre le code synchrone de manière très simple.

Vous pouvez utiliser le package "synchonize".

Juste

npm install synchronize

Alors var sync = require(synchronize);

Mettez la logique qui doit être synchrone dans une fibre en utilisant

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

Un exemple pour deux requêtes 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;
    });
}

Lorsque "saveSomething ()" est appelé, il insère une ligne dans une table principale et reçoit le dernier identifiant inséré. Après cela, le code ci-dessous sera exécuté. Pas besoin de promesses de nidification ou de trucs comme ça.

3
EscapeNetscape

Vous pouvez utiliser des fibres pour écrire du code pseudo-synchrone avec Node.JS jetez un œil à ces tests pour DB https://github.com/alexeypetrushin/mongo-lite/blob/master/test/collection.coffee ils sont asynchrones mais ressemblent à des synchrones, plus de détails http://alexeypetrushin.github.com/synchronize

1
Alexey Petrushin

première option: si toutes vos requêtes sont liées les unes aux autres, créez une procédure stockée, mettez-y toute votre logique de données et ayez une seule db.execute

option deux: si votre base de données utilise une connexion, alors commande une exécution garantie en série et vous pouvez l'utiliser comme assistant asynchrone

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
});
1
Andrey Sidorov