Search code examples
javascriptnode.jsasynchronousasync-awaitknex.js

Transaction Complete Err when calling async function within async function


I have the following async function to duplicate a record within a postgres database table called 'Questionnaires':

const duplicateQuestionnaire = async(trx, originalQuestionnaireId) => {
  const originalQuestionnaire = await trx
    .select("*")
    .from("Questionnaires")
    .where({
      id: originalQuestionnaireId
    })
    .then(head);

  const newQuestionnaireId = await trx
    .table("Questionnaires")
    .insert(omit(originalQuestionnaire, "id"))
    .returning("id")
    .then(head);

  const sectionIDs = await trx
    .select("id")
    .from("Sections")
    .where({
      QuestionnaireId: originalQuestionnaireId
    });

  sectionIDs.map(element => {
    duplicateSection(trx, element.id, newQuestionnaireId);
  });

  return trx
    .select("*")
    .from("Questionnaires")
    .where({
      id: newQuestionnaireId
    })
    .then(head);
};

After duplicating, it will call a second async function to duplicate a record within a table called 'Sections':

const duplicateSection = async (trx, sectionId, newQuestionnaireId) => {
  const originalSection = await trx
    .select("*")
    .from("Sections")
    .where({ id: sectionId })
    .then(head);

  const newSection = omit(originalSection, "id");

  newQuestionnaireId ? (newSection.QuestionnaireId = newQuestionnaireId) : null;

  const newSectionId = await trx
    .table("Sections")
    .insert(newSection)
    .returning("id")
    .then(head);

  return trx
    .select("*")
    .from("Sections")
    .where({ id: newSectionId })
    .then(head);
};

These functions are designed with a hierarchy in mind: duplicateQuestionnaire calls duplicateSection as a questionnaire contains sections. The latter function can be called without calling the former initially and does not call it as part of its actions.

The return value of each function is used as a GraphQL return.

These functions both have the desired effect on the database; they work. However, duplicateQuestionnaire is flagging this error:

(node:172) UnhandledPromiseRejectionWarning: Error: Transaction query already complete, run with DEBUG=knex:tx for more info
web_1  |     at completedError (/app/node_modules/knex/lib/transaction.js:303:9)
web_1  |     at /app/node_modules/knex/lib/transaction.js:277:22
web_1  | From previous event:
web_1  |     at Client_PG.trxClient.query (/app/node_modules/knex/lib/transaction.js:275:34)
web_1  |     at Runner.<anonymous> (/app/node_modules/knex/lib/runner.js:155:36)
web_1  | From previous event:
web_1  |     at /app/node_modules/knex/lib/runner.js:61:21
web_1  |     at runCallback (timers.js:810:20)
web_1  |     at tryOnImmediate (timers.js:768:5)
web_1  |     at processImmediate [as _immediateCallback] (timers.js:745:5)
web_1  | From previous event:
web_1  |     at Runner.run (/app/node_modules/knex/lib/runner.js:47:31)
web_1  |     at Builder.Target.then (/app/node_modules/knex/lib/interface.js:39:43)
web_1  |     at duplicateSection (/app/repositories/DuplicateRepository.js:49:6)
web_1  |     at <anonymous>
web_1  | (node:172) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
web_1  | (node:172) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I know the issue is around how I'm handling each duplicateSection call within the .map() but, after some research and intense googling, I'm still struggling to resolve it.


Solution

  • You want to await all promises returned by duplicateSections:

     await Promise.all(sectionIDs.map(element =>
        duplicateSection(trx, element.id, newQuestionnaireId)
     ));
    

    That not only ensures that all sections get copied before you run the final query, it also chains rejected promises.