Транзакция pg-promise с зависимыми запросами в цикле forEach выдает предупреждение об ошибке: запрос об освобождении или потере соединения

Я пытаюсь вставить связанные данные в транзакцию pg-promise. Транзакция успешно вставляет все данные правильно, но выдает предупреждение UnhandledPromiseRejectionWarning: Error: Querying against a released or lost connection..

Код, который вызывает это, был изначально:

const item = {
  batch: { batch_number: 1 },
  ingredients: [
    { amount: '12', unit: 'kg' },
    { amount: '4', unit: 'L' }
  ],
}


return await db.tx(async t => {
  const batchQueryString = pgp.helpers.insert(item.batch, null, 'ionomer_batch')
  const batchQuery = await t.one(batchQueryString + ' RETURNING ionomer_batch_id')

  item.ingredients.forEach(async ingredient => {
    const ingredientQueryString = pgp.helpers.insert(ingredient, null, 'ingredient')
    const ingredientQuery = await t.one(ingredientQueryString + ' RETURNING ingredient_id')
    await t.none(
            `INSERT INTO ionomer_batch_step(ionomer_batch_id, ingredient_id) 
            VALUES(${batchQuery.ionomer_batch_id}, ${ingredientQuery.ingredient_id})`
            )
   })

   return batchQuery
  }).then(data => {
    return {success: true, response: data}
  }).catch(error => {
    return {success: false, response: error}
})

Я заставил его работать без предупреждения, выполнив

return await db.tx(async t => {
  const batchQueryString = pgp.helpers.insert(item.batch, null, 'ionomer_batch')
  const batchQuery = await t.one(batchQueryString + ' RETURNING ionomer_batch_id')

  const ingredientQueries = []
  // this can't be async
  item.ingredients.forEach(ingredient => {
    const ingredientQueryString = pgp.helpers.insert(ingredient, null, 'ingredient')
    const ingredientQuery = t.one(ingredientQueryString + ' RETURNING ingredient_id')
    ingredientQueries.push(ingredientQuery)
   })
   const resolvedIngredientQueries = await t.batch(ingredientQueries)
   resolvedIngredientQueries.forEach(async ingredientQuery => {
     await t.none(
       `INSERT INTO ionomer_batch_step(ionomer_batch_id, ingredient_id) 
       VALUES(${batchQuery.ionomer_batch_id}, ${ingredientQuery.ingredient_id})`
      )
    })

   return batchQuery
  }).then(data => {
    return {success: true, response: data}
  }).catch(error => {
    return {success: false, response: error}
})

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


person deanna    schedule 14.05.2020    source источник
comment
Это потому, что в вашем коде вы не можете дождаться завершения item.ingredients.forEach, которое продолжает выполняться после закрытия транзакции. Отсюда ошибка.   -  person vitaly-t    schedule 14.05.2020


Ответы (2)


В дополнение к собственному ответу @deanna...

Вам действительно не нужно реализовывать цикл. Вы можете просто переназначить запросы в массив промисов, а затем разрешить их:

await db.tx(async t => {
    const batchQueryString = pgp.helpers.insert(item.batch, null, 'ionomer_batch');
    const batchQuery = await t.one(`${batchQueryString} RETURNING ionomer_batch_id`);

    const inserts = item.ingredients.map(async i => {
        const query = pgp.helpers.insert(i, null, 'ingredient');
        const ingredientQuery = await t.one(`${query} RETURNING ingredient_id`);
        return t.none(
            `INSERT INTO ionomer_batch_step(ionomer_batch_id, ingredient_id)
            VALUES($/batchQuery.ionomer_batch_id/, $/ingredientQuery.ingredient_id/)`,
            {batchQuery, ingredientQuery});
   });

    await t.batch(inserts); // settle all generated promises

    return batchQuery;
});

Кроме того, вы можете видеть из изменений, что вам никогда не следует использовать инъекцию значений ES6 таким образом. см. здесь:

ВАЖНО: Никогда не используйте зарезервированный синтаксис ${} внутри строк шаблона ES6, поскольку они не знают, как форматировать значения для PostgreSQL. Внутри строк шаблона ES6 вы должны использовать только одну из 4 альтернатив — $(), $<>, $[] или $//.

person vitaly-t    schedule 15.05.2020
comment
имеет ли значение, await ли вы t.none запрос на карте? - person deanna; 16.05.2020
comment
Если вы не await для него, то вам придется .then-связывать его. - person vitaly-t; 16.05.2020

Как сказал @vitaly-t, мне нужно было убедиться, что цикл forEach действительно завершен.

Рабочее решение есть

const item = {
  batch_number: 1,
  },
  ingredients: [
    { amount: '12', unit: 'kg' },
    { amount: '4', unit: 'L' }
  ],
}


return await db.tx(async t => {
  const batchQueryString = pgp.helpers.insert(item.batch, null, 'ionomer_batch')
  const batchQuery = await t.one(batchQueryString + ' RETURNING ionomer_batch_id')

  await asyncForEach(item.ingredients, async ingredient => {
    const ingredientQueryString = pgp.helpers.insert(ingredient, null, 'ingredient')
    const ingredientQuery = await t.one(ingredientQueryString + ' RETURNING ingredient_id')
    await t.none(
            `INSERT INTO ionomer_batch_step(ionomer_batch_id, ingredient_id) 
            VALUES(${batchQuery.ionomer_batch_id}, ${ingredientQuery.ingredient_id})`
            )
   })

   return batchQuery
  }).then(data => {
    return {success: true, response: data}
  }).catch(error => {
    return {success: false, response: error}
})

Где я должен был сделать вспомогательную функцию

async function asyncForEach(array, callback) {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
}

как описано здесь

person deanna    schedule 14.05.2020