Синхронные запросы к базе данных с узлом.js


У меня есть узел.JS / Express приложение, которое запрашивает базу данных MySQL в пределах маршрута и отображает результат пользователю. Моя проблема заключается в том, как я могу запускать запросы и блокировать, пока оба запроса не будут выполнены, прежде чем перенаправлять пользователя на запрошенную страницу?

в моем примере у меня есть 2 запроса, которые нужно закончить, прежде чем я визуализирую страницу. Я могу заставить запросы выполняться синхронно, если я вложу запрос 2 в обратный вызов "result" запроса 1. Это, однако, станет очень запутанным, когда количество запросов увеличивается.

Как выполнить несколько (в данном случае 2) запросов к базе данных синхронно без вложения последующего запроса в обратный вызов предыдущего запроса?

Я посмотрел на "управление потоком / асинхронные лакомства" в узловых модулях и попробовал flow-js, но я не могу заставить его работать с асинхронными запросами.

Ниже перечислены 2 запроса, которые я пытаюсь выполнить из маршрута '/home'. Могут ли специалисты узла объяснить "правильный" способ сделать это.

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

6 ответов:

цель с узлом-не заботиться о том, в каком порядке все происходит. Это может усложнить некоторые сценарии. Нет ничего постыдного в вложенных обратных вызовах. Как только вы привыкнете к тому, как это выглядит, вы можете обнаружить, что вы на самом деле предпочитаете этот стиль. Я делаю; это очень ясно, какой порядок обратных вызовов будет стрелять. Вы можете отказаться от анонимных функций, чтобы сделать его менее подробным, если нужно.

Если вы хотите немного реструктурировать свой код, вы можете использовать "типичный" вложенный метод обратного вызова. Если вы хотите чтобы избежать обратных вызовов, существует множество асинхронных фреймворков, которые попытаются помочь вам в этом. Тот, который вы можете проверить, является асинхронным.js (https://github.com/fjakobs/async.js). пример каждого:

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

});

для одноразового использования я бы, вероятно, просто использовал подход типа подсчета ссылок. Просто отслеживать, сколько запросов вы хотите выполнить и представить ответ, когда они все закончили.

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

еще более приятным подходом было бы просто позвонить finishRequest () в каждом обратном вызове "result" проверяет наличие непустых массивов перед отображением ответа. Будет ли это работать в вашем случае, зависит от ваших требований.

вот очень простой трюк для обработки нескольких обратных вызовов.

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

пример

использование:

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

Так что в основном вам нужен подсчет ссылок. Это стандартный подход в таких ситуациях. Я на самом деле использую эту небольшую функцию полезности вместо управления потоком.

Если вы хотите полномасштабное решение для управления потоком, я бы рекомендовал futuresJS

Я считаю, что асинхронная библиотека лучше всего подходит для таких вещей. https://github.com/caolan/async#parallel

Я не могу проверить это или ничего, так что простите, если есть опечатки. Я изменил вашу функцию запроса, чтобы ее можно было использовать повторно. Таким образом, вызов queryRows вернет функцию, которая соответствует формату параллельных функций обратного вызова асинхронного модуля. После завершения обоих запросов он вызовет последнюю функцию и передаст результат двух запросов в виде аргумент, который вы можете прочитать, чтобы передать в ваш шаблон.

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

здесь есть некоторые решения, но, на мой взгляд, лучшее решение-сделать код синхронно очень простым способом.

вы можете использовать "пакет synchonize".

просто

npm install synchronize

затем var sync = require(synchronize);

положите логику, которая должна быть синхронной в волокно с помощью

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

пример для двух запросов 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;
    });
}

когда вызывается " saveSomething ()", он вставляет строку в основную таблицу и получает последний вставленный идентификатор. После этого будет выполнен приведенный ниже код. Нет необходимости в гнездовых обещаниях или тому подобном.

вариант первый: если все ваши запросы связаны друг с другом, создайте хранимую процедуру, поместите в нее всю логику данных и получите один db.execute

вариант второй: если ваша БД использует одно соединение, то команды гарантированно будут выполняться последовательно, и вы можете использовать это как асинхронный помощник

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

вы можете использовать волокна, чтобы написать псевдо-синхронный код с узлом.JS взгляните на эти тесты для DB https://github.com/alexeypetrushin/mongo-lite/blob/master/test/collection.coffee они асинхронны, но выглядят синхронно, подробнее http://alexeypetrushin.github.com/synchronize