Синхронные запросы к базе данных с Node.js - программирование
Подтвердить что ты не робот

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

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

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

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

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

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

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

Ответ 1

Цель с 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() в каждом обратном вызове результата, чтобы проверить непустые массивы, прежде чем вы отнесите ответ. Будет ли это работать в вашем случае, зависит от ваших требований.

Ответ 2

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

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

Ответ 3

Я нахожу, что асинхронная библиотека лучше всего подходит для таких вещей. 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} 
    });
  });
});

Ответ 4

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

Вы можете использовать пакет 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()", он вставляет строку в основную таблицу и получает последний вставленный идентификатор. После этого будет выполнен код ниже. Нет необходимости вложенности promises или тому подобного.

Ответ 5

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

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

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