How to cancel execution of async calls in a map function

113 Views Asked by At

I am running a process of extracting codes from pages in a PDF file. The problem is that when I hit the cancel button. The process does not get canceled and keeps reading codes from the file. I am using abortController to abort the process.

Here is function scanCodes:

export const scanCodes = async ({
  setLogs,
  setProgress,
  pdfPath,
  patientsFolder,
  cropCords,
  pdf,
  abortControllerSignal,
  sortToPatients,
}) =>
  new Promise(async (resolve, reject) => {
    const pageCodes = [];
    const newFiles = [];
    const pageLength = await configService.get('page-length');

    setLogs(i18n.t('MAIN_CONTENT.CODESCAN.SUBHEADER.SCAN_STATE.STARTING'));

    if (!model) {
      wasmBinary = await loadWasmBinary();
      engine = await createOCREngine({ wasmBinary });
      const modelPath = app.isPackaged
        ? path.join(process.resourcesPath, 'public/lang-data/eng.traineddata')
        : path.resolve('public/lang-data/eng.traineddata');
      model = await fs.promises.readFile(modelPath);
      await engine.loadModel(model);
      await engine.setVariable('tessedit_pageseg_mode', '7');
      await engine.setVariable('tessedit_char_whitelist', ' 0123456789');
    }

    setLogs(i18n.t('MAIN_CONTENT.CODESCAN.SUBHEADER.SCAN_STATE.SCANNING'));

    await Promise.all(
      Array.from({ length: pdf._pdfInfo.numPages }).map(async (x, i) => {
        const cropCordsConfig = await configService.get('ocr-position');
        const cords = {
          x: cropCordsConfig?.x || cropCords.x,
          y: cropCordsConfig?.y || cropCords.y,
          width: cropCordsConfig?.width || cropCords.width,
          height: cropCordsConfig?.height || cropCords.height,
        };
        const pageImage = await pdfService.readAsImageData(pdf, i + 1, cords);

        await engine.loadImage(pageImage);
        const text = engine.getText();
        pageCodes.push({
          code: text.replace(/\n/gi, ''),
          pageIndex: i,
        });
        setProgress(parseInt((pageCodes.length / pdf._pdfInfo.numPages) * 100));
      })
    );
    setProgress(0);
    setLogs(i18n.t('MAIN_CONTENT.CODESCAN.SUBHEADER.SCAN_STATE.ANALYZING'));

    const pagesWithInvalidCodeFormat = [];
    const invalidCustIdFiles = [];
    const validCustIdFiles = [];
    const custId = await configService.get('custId');

    pageCodes.forEach((c, i) => {
      const validity = validateCode(c.code, pageLength);
      if (validity === null) {
        // Do not replace with ===
        if (parseCode(c.code, pageLength).custId == custId) {
          validCustIdFiles.push(c);
        } else {
          invalidCustIdFiles.push(c);
        }
      } else {
        pagesWithInvalidCodeFormat.push({ ...c, error: validity });
      }
      return validity === null;
    });

    let docSorted = _.groupBy(invalidCustIdFiles, (item) => parseCode(item.code, pageLength).patientId);
    docSorted = Object.keys(docSorted).map((key) => docSorted[key]);

    const invalidDocuments = await groupByAsync(
      docSorted.flat(),
      async (item) => `Docs-CN-${parseCode(item.code, pageLength).custId}`
    );
    const documents = await groupByAsync(validCustIdFiles, async (item) =>
      documentId(parseCode(item.code, pageLength))
    );
    const missingPageNumbers = {};

    for (let docId of Object.keys(documents)) {
      const docPages = documents[docId];
      const docTotalPages = parseCode(docPages[0].code, pageLength).totalPages;
      missingPageNumbers[docId] = [...new Array(parseInt(docTotalPages)).keys()]
        // Do not replace with ===
        .filter((i) => !docPages.find((p) => parseCode(p.code, pageLength).pageNo == i + 1))
        .map((p) => p + 1);
      setProgress(parseInt((Object.keys(documents).indexOf(docId) / Object.keys(documents).length) * 100));
    }

    setProgress(0);
    setLogs(i18n.t('MAIN_CONTENT.CODESCAN.SUBHEADER.SCAN_STATE.CREATING'));

    const docPromises = Object.keys(documents).map(async (docId) => {
      if (abortControllerSignal && abortControllerSignal.aborted) {
        setLogs('ready');
        setProgress(0);
        return;
      }

      if (missingPageNumbers[docId].length) return;
      const docPages = documents[docId];
      // create pdf file
      const newExtractedPdf = await pdfService.extractPagesToPdf({
        file: await fs.promises.readFile(pdfPath),
        pages: docPages
          .sort(
            (a, b) => parseInt(parseCode(a.code, pageLength).pageNo) - parseInt(parseCode(b.code, pageLength).pageNo)
          )
          .map((p) => ({ originalPageNumber: parseInt(p.pageIndex) + 1 })),
      });

      const newFileName = `${docId}.pdf`;
      const newFilePath = path.join(patientsFolder, newFileName);

      let targetDir = newFilePath;
      if (sortToPatients) {
        const fileName = path.basename(newFileName);
        const splitFileName = fileName.split('-');
        const patientID = splitFileName[1];
        targetDir = path.join(patientsFolder, patientID);
        if (!fse.existsSync(targetDir)) {
          fse.mkdirs(targetDir);
        }
        targetDir = path.join(targetDir, path.basename(newFileName));
      }
      await fs.promises.writeFile(targetDir, newExtractedPdf).catch((err) => {
        alert(err.message ? err.message : err.toString());
      });

      newFiles.push(newFilePath);
      setProgress(100);
    });
    await Promise.all(docPromises);

    if (invalidDocuments.length) {
      setProgress(0);
      setLogs('Moving files to SplitScan...');
    }

    const splitScanPath = await configService.get('splitscan-path');
    const invalidDocPromises = Object.keys(invalidDocuments).map(async (docId) => {
      if (abortControllerSignal && abortControllerSignal.aborted) {
        setLogs(i18n.t('MAIN_CONTENT.CODESCAN.SUBHEADER.SCAN_STATE.READY'));
        setProgress(0);
        return;
      }

      const docPages = invalidDocuments[docId];
      // create pdf file
      const newExtractedPdf = await pdfService.extractPagesToPdf({
        file: await fs.promises.readFile(pdfPath),
        pages: docPages.map((p) => ({ originalPageNumber: parseInt(p.pageIndex) + 1 })),
      });

      const newFileName = `${docId}.pdf`;
      const newFilePath = path.join(splitScanPath, newFileName);

      await fs.promises.writeFile(newFilePath, newExtractedPdf).catch((err) => {
        alert(err.message ? err.message : err.toString());
      });
      setProgress(100);
    });
    await Promise.all(invalidDocPromises);

    const pagesOfDocumentsWithMissingPages = Object.entries(missingPageNumbers)
      .filter(([_key, value]) => value.length)
      .map(([doc, _pages]) => documents[doc])
      .map((doc) => Object.fromEntries(doc.map(({ pageIndex, code }) => [pageIndex, code])))
      .reduce((a, b) => ({ ...a, ...b }), {});

    const pagesHavingErrors = Object.fromEntries(
      pagesWithInvalidCodeFormat.map(({ pageIndex, code }) => [pageIndex, code])
    );
    const pagesExcludedFromResult = { ...pagesOfDocumentsWithMissingPages, ...pagesHavingErrors };
    let errorsFilePath = null;

    if (Object.keys(pagesExcludedFromResult).length) {
      // save error pages
      errorsFilePath = path.join(path.dirname(pdfPath), `${path.basename(pdfPath, path.extname(pdfPath))}_errors.pdf`);
      const pagesToBeExcluded = Object.keys(pagesExcludedFromResult).sort((a, b) => parseInt(a) - parseInt(b));
      const errorPdfFileContents = await pdfService
        .extractPagesToPdf({
          file: await fs.promises.readFile(pdfPath),
          pages: pagesToBeExcluded.map((pNo) => ({ originalPageNumber: parseInt(pNo) + 1 })),
        })
        .catch((err) => {
          alert('could not extract error pages !');
          console.log(err);
        });

      const errorsFileMappingPath = getMappingFile(errorsFilePath);

      await fs.promises.writeFile(errorsFilePath, errorPdfFileContents).catch(reject);

      await fs.promises
        .writeFile(
          errorsFileMappingPath,
          JSON.stringify(
            pagesToBeExcluded.map((p) => pagesExcludedFromResult[p]),
            1
          )
        )
        .catch(reject);
    }

    setProgress(100);

    resolve([
      Object.fromEntries(pagesWithInvalidCodeFormat.map((i) => [`#${i.pageIndex + 1} | '${i.code}'`, i.error])),
      Object.fromEntries(Object.entries(missingPageNumbers).filter(([_key, value]) => value.length)),
      newFiles,
      errorsFilePath,
    ]);
  });

The process was aborting previously when I was using a simple for loop instead of Promise.all and 'async map calls`

Here is the Button Code:

<ActionBarButton className="SS-C-code-scan-action-bar_run" onClick={() => abortController?.abort() || undefined}> {i18n.t('MAIN_CONTENT.CODESCAN.SUBHEADER.BUTTON.CANCEL')} </ActionBarButton>

I have tried different solution tried to shift abortControllerSignal if statement to different locations out of the Promise.all. Then I declared a function that sets the cancelToken value to true But the true value was only accessible when the Promise was resolved. Any help would be appreciated

0

There are 0 best solutions below