Синхронные запросы базы данных с Node.Яш

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

в моем примере у меня есть 2 запроса, которые нужно закончить, прежде чем я визуализирую страницу. Я могу заставить запросы работать синхронно, если я вложу запрос 2 внутри обратного вызова "результата" запроса 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 ответов


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

Если вы хотите немного реструктурировать свой код, вы можете использовать "типичный" вложенный метод обратного вызова. Если вы хотите чтобы избежать обратных вызовов, существует множество асинхронных фреймворков, которые попытаются помочь вам в этом. Один, который вы можете проверить, - это async.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 установить синхронизировать

затем 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