Async/Await Node-Postgres Queries в циклах ForEach

РЕДАКТИРОВАТЬ: я использую узел v8.0.0

Я только начал изучать, как обращаться к базам данных SQL с помощью node-postgres, и у меня возникли небольшие проблемы с доступом к нескольким базам данных для сбора данных в работоспособном формате, особенно с выполнением нескольких запросов в циклах forEach. После нескольких попыток я пытаюсь выполнить async/await, но получаю следующую ошибку:

await client.connect()
  ^^^^^^
SyntaxError: Unexpected identifier

Когда я пытался использовать пул или вызывал.query последовательно, я получал что-то вроде

1
[]
could not connect to postgres Error: Connection terminated

Вот сокращенная версия моего кода:

const { Client } = require('pg');
const moment = require('moment');
const _ = require('lodash');
const turf = require('@turf/turf');

const connString = // connection string
var collected = []
const CID = 300
const snaptimes = // array of times
var counter=0;
const client = new Client(connString);

function createArray(i,j) {
     // return array of i arrays of length j
}

await client.connect()

snaptimes.forEach(function(snaptime){
  var info = {}; // an object of objects
  // get information at given snaptime from database 1
  const query1 = // parametrized query selecting two columns from database 1
  const result1 = await client.query(query1, [CID,snaptime]);
  var x = result1.rows;
  for (var i = 0; i < x.length; i++) {
     // store data from database 1 into info
     // each row is an object with two fields
  }

  // line up subjects on the hole
  const query2 = // parametrized query grabbing JSON string from database 2
  const result2 = await client.query(query2, [CID,snaptime]);
  const raw = result2.rows[0].JSON_col;
  const line = createArray(19,0); // an array of 19 empty arrays
  for (var i = 0; i < raw.length; i++) {
     // parse JSON object and record data into line 
  }

  // begin to collect data
  var n = 0;
  var g = 0;
  // walk down the line
  for (var i = 18; i > 0; i--) {
    // if no subjects are found at spot i, do nothing, except maybe update g
    if ((line[i] === undefined || line[i].length == 0) && g == 0){
      g = i;
    } else if (line[i] !== undefined && line[i].length != 0) {
      // collect data for each subject if subjects are found
      line[i].forEach(function(subject){
        const query 3 = // parametrized query grabbing data for each subject 
        const result3 = await client.query(query3,[CID,subject,snaptime]);
        x = result3.rows;
        const y = moment(x[0].end_time).diff(moment(snaptime),'minutes');
        var yhat = 0;
        // the summation over info depends on g
        if (g===0){
          for (var j = i; j <= 18; j++){
            yhat = moment.duration(info[j].field1).add(yhat,'m').asMinutes();
          }
        } else {
          for (var j = i; j <= 18; j++){
            if (i<j && j<g+1) {
              yhat = moment.duration(info[j].field2).add(yhat,'m').asMinutes();
            } else {
              yhat = moment.duration(info[j].field1).add(yhat,'m').asMinutes();
            }
          }
        }
        collected.push([y,yhat,n,i]);
      });
    }
    n+=line[i].length;
    g=0;
  }
  // really rough work-around I once used for printing results after a forEach of queries
  counter++;
  if (counter===snaptimes.length){
    console.log(counter);
    console.log(collected);
    client.end(); 
  }
});

3 ответа

Решение

Проблема вызвана тем, что ваш обратный вызов forEach не async:

snaptimes.forEach(function(snaptime){

должно быть:

snaptimes.forEach(async function (snaptime) {

для await быть узнаваемым на всех.

Имейте в виду, что async Функция немедленно возвращается и возвращает обещание, которое в конечном итоге разрешается return заявления async функция (или отклонена с неисследованными исключениями, поднятыми внутри async функция).

Но также убедитесь, что ваша версия Node поддерживает async/await:

  • Начиная с узла 7.6 его можно использовать без --harmony флаг.
  • В Node 7.x до 7.6 вы должны использовать --harmony флаг.
  • Он не был доступен в узле до 7.0.

Смотрите: http://node.green/#ES2017-features-async-functions

Также обратите внимание, что вы можете использовать await только внутри функций, объявленных с async ключевое слово. Если вы хотите использовать его на верхнем уровне вашего скрипта или модуля, вам нужно обернуть его в немедленно вызванное выражение функции:

// cannot use await here
(async () => {
  // can use await here
})();
// cannot use await here

Пример:

const f = () => new Promise(r => setTimeout(() => r('x'), 500));

let x = await f();
console.log(x);

печатает:

$ node t1.js 
/home/rsp/node/test/prom-async/t1.js:3
let x = await f();
              ^
SyntaxError: Unexpected identifier

но это:

const f = () => new Promise(r => setTimeout(() => r('x'), 500));

(async () => {
  let x = await f();
  console.log(x);
})();

печатает:

$ node t2.js 
x

с задержкой 0,5 с, как и ожидалось.

На версиях Node, которые не поддерживают async/await первый (неверный) пример напечатает:

$ ~/opt/node-v6.7.0/bin/node t1.js 
/home/rsp/node/test/prom-async/t1.js:3
let x = await f();
              ^
SyntaxError: Unexpected identifier

и второй (правильный) пример выведет другую ошибку:

$ ~/opt/node-v6.7.0/bin/node t2.js 
/home/rsp/node/test/prom-async/t2.js:3
(async () => {
       ^
SyntaxError: Unexpected token (

Это полезно знать, потому что версии Node, которые не поддерживают async/await К сожалению, вы не получите значимую ошибку, например "async / await не поддерживается" или что-то в этом роде.

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

Более того, проблема с вашим кодом состоит в том, что вы можете только await внутри async функции; Вы делаете это вне какой- либо функции.

Прежде всего, вот решение, наиболее близкое к написанному вами коду:

const { Client } = require('pg');
const moment = require('moment');
const _ = require('lodash');
const turf = require('@turf/turf');

const connString = // connection string
var collected = []
const CID = 300
const snaptimes = // array of times
var counter=0;
const client = new Client(connString);

function createArray(i,j) {
    // return array of i arrays of length j
}

async function processSnaptime (snaptime) {
    var info = {}; // an object of objects
    // get information at given snaptime from database 1
    const query1 = // parametrized query selecting two columns from database 1
    const result1 = await client.query(query1, [CID,snaptime]);
    var x = result1.rows;
    for (var i = 0; i < x.length; i++) {
      // store data from database 1 into info
      // each row is an object with two fields
    }

    // line up subjects on the hole
    const query2 = // parametrized query grabbing JSON string from database 2
    const result2 = await client.query(query2, [CID,snaptime]);
    const raw = result2.rows[0].JSON_col;
    const line = createArray(19,0); // an array of 19 empty arrays
    for (var i = 0; i < raw.length; i++) {
      // parse JSON object and record data into line
    }

    // begin to collect data
    var n = 0;
    var g = 0;
    // walk down the line
    for (var i = 18; i > 0; i--) {
      // if no subjects are found at spot i, do nothing, except maybe update g
      if ((line[i] === undefined || line[i].length == 0) && g == 0){
        g = i;
      } else if (line[i] !== undefined && line[i].length != 0) {
        // collect data for each subject if subjects are found
        line[i].forEach(function(subject){
          const query 3 = // parametrized query grabbing data for each subject
          const result3 = await client.query(query3,[CID,subject,snaptime]);
          x = result3.rows;
          const y = moment(x[0].end_time).diff(moment(snaptime),'minutes');
          var yhat = 0;
          // the summation over info depends on g
          if (g===0){
            for (var j = i; j <= 18; j++){
              yhat = moment.duration(info[j].field1).add(yhat,'m').asMinutes();
            }
          } else {
            for (var j = i; j <= 18; j++){
              if (i<j && j<g+1) {
                yhat = moment.duration(info[j].field2).add(yhat,'m').asMinutes();
              } else {
                yhat = moment.duration(info[j].field1).add(yhat,'m').asMinutes();
              }
            }
          }
          collected.push([y,yhat,n,i]);
        });
      }
      n+=line[i].length;
      g=0;
    }
    // really rough work-around I once used for printing results after a forEach of queries
    counter++;
    if (counter===snaptimes.length){
      console.log(counter);
      console.log(collected);
    }
}

async function run () {
  for (let snaptime of snaptimes) {
    await processSnaptime(snaptime);
  }
}

/* to run all of them concurrently:
function run () {
  let procs = [];
  for (let snaptime of snaptimes) {
    procs.push(processSnaptime(snaptime));
  }
  return Promise.all(procs);
}
*/

client.connect().then(run).then(() => client.end());

client.connect возвращает обещание, и я использую then звонить run как только это решено. Когда эта часть закончена, client.end() можно смело вызывать.

run является async функция, поэтому он может использовать await сделать код более читабельным. То же самое касается processSnaptime,

Конечно, я не могу на самом деле запустить ваш код, поэтому я могу только надеяться, что не допустил ошибок.

Убедитесь, что вы должны использовать async блок снаружи, как:

async function() {
  return await Promise.resolve('')
}

И это по умолчанию поддерживается после узла 7.6.0. До 7.6.0 вы должны использовать --harmony возможность работать на это.

node -v Сначала проверьте свою версию.

Другие вопросы по тегам