Асинхронные запросы к базе данных с PostgreSQL в узле не работают

Используя Node.js и модуль node-postgres для связи с базой данных, я пытаюсь написать функцию, которая принимает массив запросов и обратных вызовов и выполняет их все асинхронно, используя одно и то же соединение с базой данных. Функция принимает двумерный массив и вызов ее выглядит так:

perform_queries_async([
  ['SELECT COUNT(id) as count FROM ideas', function(result) {
    console.log("FUNCTION 1");
  }],
  ["INSERT INTO ideas (name) VALUES ('test')", function(result) {
    console.log("FUNCTION 2");
  }]
]);

И функция выполняет итерацию по массиву, создавая запрос для каждого подмассива, например:

function perform_queries_async(queries) {
  var client = new pg.Client(process.env.DATABASE_URL);

  for(var i=0; i<queries.length; i++) {
    var q = queries[i];

    client.query(q[0], function(err, result) {
      if(err) {
        console.log(err);
      } else {
        q[1](result);
      }
    });
  }

  client.on('drain', function() {
    console.log("drained");
    client.end();
  });

  client.connect();
}

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

FUNCTION 1
FUNCTION 2
drained

Однако вывод странным образом выглядит так:

FUNCTION 2
drained
FUNCTION 2

Мало того, что вторая функция вызывается для обоих запросов, также кажется, что код слива вызывается до того, как очередь запросов клиента завершится... но второй запрос по-прежнему работает отлично, даже несмотря на то, что код client.end() якобы убит клиент после вызова события.

Я рвал на себе волосы об этом часами. Я попытался жестко запрограммировать свой массив образцов (таким образом удалив цикл for), и мой код работал, как и ожидалось, что наводит меня на мысль, что в моем цикле есть какая-то проблема, которую я не вижу.

Буду очень признателен за любые идеи о том, почему это может происходить.


person maxluzuriaga    schedule 18.11.2013    source источник


Ответы (3)


Самый простой способ правильно зафиксировать значение переменной q в замыкании в современном JavaScript — использовать forEach:

queries.forEach(function(q) {
    client.query(q[0], function(err, result) {
      if(err) {
        console.log(err);
      } else {
        q[1](result);
      }
    });
 });

Если вы не фиксируете значение, ваш код отражает последнее значение, которое имело q, как функцию обратного вызова, выполненную позже, в контексте содержащей функции.

forEach, используя функцию обратного вызова, изолирует и фиксирует значение q, чтобы его можно было правильно оценить с помощью внутреннего обратного вызова.

person WiredPrairie    schedule 18.11.2013
comment
Ах, спасибо большое, это сработало отлично. Я по-прежнему сталкиваюсь с проблемой, когда drained не появляется в ожидаемом месте, но, очевидно, это больше проблема с библиотекой node-postgres, чем с синтаксисом цикла. - person maxluzuriaga; 19.11.2013

Жертва знаменитой ошибки замыкания/цикла Javascript. Смотрите мои (и другие) ответы здесь:

Я пытаюсь открыть 10 веб-сокетов с помощью nodejs, но мой цикл почему-то не работает

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

person Nitzan Shaked    schedule 18.11.2013

Было бы хорошо выполнить это с помощью асинхронного модуля. Это также поможет вам повторно использовать код. и сделает код более читабельным. Мне просто нравится автоматическая функция, предоставляемая асинхронным модулем. Ссылка: https://github.com/caolan/async.

person Abhimanyu    schedule 16.12.2014