Requêtes de firebase database synchrones avec Node.js

J’ai une application Node.js / Express qui interroge une firebase database MySQL dans la route 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 redirect l’utilisateur vers la page demandée.

Dans mon exemple, j’ai 2 requêtes à terminer avant de rendre la page. Je peux faire en sorte que les requêtes s’exécutent de manière synchrone si j’imprime la requête 2 dans le rappel «résultat» de la requête 1. Cela deviendra toutefois très compliqué lorsque le nombre de requêtes augmentera.

Comment puis-je exécuter plusieurs requêtes de firebase database (dans ce cas 2) sans synchroniser la requête suivante dans le callback “result” de la requête précédente?

J’ai regardé les «goodies de contrôle de stream / async» dans les modules Node et essayé les stream-js mais je ne peux pas les faire fonctionner avec les requêtes asynchrones.

Vous trouverez ci-dessous la liste des 2 requêtes que je tente d’exécuter à partir de l’itinéraire «/ 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 }}); }); 

Le but avec node n’est pas de savoir dans quel ordre les choses se passent. Cela peut compliquer certains scénarios. Il n’y a pas de honte dans les rappels de nidification. Une fois que vous avez l’habitude, vous pouvez trouver que vous préférez ce style. Je fais; il est très clair que les rappels d’ordre vont se déclencher. Vous pouvez renoncer aux fonctions anonymes pour le rendre moins verbeux si nécessaire.

Si vous souhaitez restructurer un peu votre code, vous pouvez utiliser la méthode de rappel nestede “typique”. Si vous souhaitez éviter les rappels, il existe de nombreux frameworks asynchrones qui vous aideront à le faire. Vous pouvez en choisir un, 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 utilisation unique, j’utiliserais probablement une approche de type comptage de référence. Il suffit de garder une trace du nombre de requêtes que vous souhaitez exécuter et de rendre la réponse lorsqu’elles sont 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 intéressante serait d’appeler simplement finishRequest () dans chaque rappel «résultat», une vérification des tableaux non vides avant que vous ne rendiez la réponse. Que cela fonctionne dans votre cas dépend de vos besoins.

Voici un truc très facile 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

Usage:

 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 avez besoin de compter les références. C’est l’approche standard dans ces situations. En fait, j’utilise cette petite fonction utilitaire au lieu du contrôle de stream.

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

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, 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, appeler queryRows renverra une fonction correspondant au format des fonctions de rappel parallèles 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 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} }); }); }); 

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 installation synchronise

Ensuite, var sync = require(synchronize);

Mettez une logique qui devrait ê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 choses comme ça.

option 1: 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 firebase database utilise une connexion, elle commande une exécution garantie 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 }); 

Vous pouvez utiliser des fibres pour écrire du code pseudo-synchrone avec Node.JS jetez un oeil à ces tests pour la firebase database https://github.com/alexeypetrushin/mongo-lite/blob/master/test/collection.coffee mais ils sont d’asynchronisme. comme synchrone, plus de détails http://alexeypetrushin.github.com/synchronize