Challenges with implementing asynchronous functions in NestJS controllers

Currently, I am in the process of developing a finance tracker application that involves importing data from a CSV file. The import functionality checks if an entry already exists in the database, adds a specific category to it if not found, and then saves it to the database.

After importing the CSV file, I intend to provide a response to the Post Request that includes the number of imported entries and details of entries for which no suitable category was found. However, due to the asynchronous nature of Nestjs, I encounter issues where my response is output before all functions are completed. This leads to the initial import always showing “imports: 0” and “unsortedTransactions: []”

I am seeking guidance on how to ensure that the controller waits for all functions to complete before returning a response.

TransactionController:
@Controller('transactions')
export class TransactionController {
  constructor(
    private transactionService: TransactionService,
    private labelService: LabelService,
  ) {}

  @Post()
  @UseInterceptors(
    FileInterceptor('file', {
      storage: diskStorage({
        destination: './uploads/csv',
        filename: randomFilename,
      }),
    }),
  )
  public createTransactions(
    @Response() response,
    @UploadedFile() file,
    @Request() request,
  ) {
    let imports = 0;

    fs.createReadStream(path.resolve(file.path), {
      encoding: 'utf-8',
    })
      .pipe(csv.parse({ headers: false, delimiter: ';', skipRows: 1 }))
      .on('data', (data) => {
        const timestamp = new Date()
          .toISOString()
          .slice(0, 19)
          .replace('T', ' ');
        const amount = parseFloat(data[14].replace(',', '.'));
        const bankaccountId = request.body.bankaccount_id;
        const bookingDate = this.createDate(data[1]);
        const name = data[11].replace(/\s\s+/g, ' ');
        const usage = data[4].replace(/\s\s+/g, ' ');

        this.transactionService
          .checkDuplicate(amount, name, usage, bookingDate)
          .then((entry) => {
            if (entry.length === 0) {
              this.sortLabel(data).then((label_id) => {
                const newTransaction = {
                  amount,
                  name,
                  usage,
                  label_id,
                  bankaccount_id: bankaccountId,
                  booking_date: bookingDate,
                  created_at: timestamp,
                  updated_at: timestamp,
                };

                this.transactionService.create(newTransaction);
                imports++;
              });
            }
          });
      });

    const unsortedTransactions = this.transactionService.getUnsorted();
    return response.send({ unsortedTransactions, imports });
  }

  private async sortLabel(transaction: Transaction): Promise<any> {
    let label_id = 1;
    const labels = await this.labelService.getAll();
    const name = transaction[11].replace(/\s\s+/g, ' ').toLowerCase();
    const usage = transaction[4].replace(/\s\s+/g, ' ').toLowerCase();

    labels.forEach((label) => {
      if (label.keywords != null) {
        const keywords = label.keywords.split(',');

        keywords.forEach((keyword) => {
          if (
            name.includes(keyword.toLowerCase()) ||
            usage.includes(keyword.toLowerCase())
          ) {
            label_id = label.id;
          }
        });
      }
    });

    return await label_id;
  }

  private createDate(date: string): string {
    const dateArray = date.split('.');

    return `20${dateArray[2]}-${dateArray[1]}-${dateArray[0]}`;
  }
}

Transaction Service:

export class TransactionService {
  constructor(
    @InjectRepository(Transaction)
    private readonly transactionRepository: Repository<Transaction>,
  ) {}

  public create(transaction): Promise<Transaction> {
    return this.transactionRepository.save(transaction);
  }

  public getAll(): Promise<Transaction[]> {
    return this.transactionRepository.find({
      relations: ['label_id', 'bankaccount_id'],
    });
  }

  public getUnsorted(): Promise<Transaction[]> {
    return this.transactionRepository.find({
      where: {
        label_id: 1,
      },
    });
  }

  public checkDuplicate(
    amount: number,
    name: string,
    usage: string,
    booking_date: string,
  ): Promise<Transaction[]> {
    return this.transactionRepository.find({
      where: {
        amount,
        name,
        usage,
        booking_date,
      },
    });
  }
}

Answer №1

To optimize your JavaScript code, utilize the power of `async` / `await`. Since you are already working with promises in your service, this process should be straightforward.

Implement the following modifications:

Introduce the async keyword to a function (enabling you to await other functions within it)

  public createTransactions(
    @Response() response,
    @UploadedFile() file,
    @Request() request,
  ) async {....
  fs.createReadStream(path.resolve(file.path), {
      encoding: 'utf-8',
    })
      .pipe(csv.parse({ headers: false, delimiter: ';', skipRows: 1 }))
      .on('data', async (data) => {

        //... omitted for brevity

        const checkDuplicateRes = await this.transactionService.checkDuplicate(
          amount,
          name,
          usage,
          bookingDate,
        );

        if (checkDuplicateRes.length === 0) {
          const labelId = await this.sortLabel(data);
          const newTransaction = {
            amount,
            name,
            usage,
            label_id: labelId,
            bankaccount_id: bankaccountId,
            booking_date: bookingDate,
            created_at: timestamp,
            updated_at: timestamp,
          };

          await this.transactionService.create(newTransaction);
          imports++;
        }
        const unsortedTransactions = await this.transactionService.getUnsorted();
        return response.send({ unsortedTransactions, imports });
      });

  }

In essence, we are replacing the `.then` syntax with the above approach. If you are unfamiliar with this method, I recommend researching it as it provides clearer code structure compared to chaining multiple `.then()` statements.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Issue with Ng-Messages not functioning properly due to a custom ng-Include directive lacking a child scope

I have created a custom directive called lobInclude, which is similar to ngInclude but with no isolated scope: .directive("lobInclude", ["$templateRequest", "$compile", function($templateRequest, $compile) { return { restrict: "A", ...

Whenever I attempt to run my script, the console on replit fails to function properly

As a complete beginner to Javascript, I'm having trouble getting my script to respond when I try to execute it. It just moves on to the next line and waits for me to type 'node index.js' again. I've included 2 images in an Imgur album - ...

Tips for Sending Specific Row Information to Server in Ionic 3

Successfully pulled contacts from the device into my list page, but struggling to send specific contact details (name and number) to the server. Can anyone provide guidance on accomplishing this in Ionic? ...

jQuery is unable to locate the FireBreath object

Currently, I am in the process of developing a web video player by using Firebreath to compile my C/C++ codec as a browser plugin. The plugin has been successfully loaded and is functioning properly (start, stop video, etc.). My next goal is to enable full ...

Using a comma format while typing in Angular ensures that the input field

When using jqxwidget from here, the default format includes commas separated by underscores. I am looking to have the field empty initially, with commas appearing as the user types - similar to a F2 cell renderer. For example, typing 100 should display a ...

My attempts to load the .mtl and .obj files using THREE.js have been unsuccessful

I've been working on creating a 3D model viewer using three.js, but I'm encountering issues with loading .mtl and .obj files. Despite following a tutorial at , the only model that loads successfully is the female one. Here is the code snippet I ...

The Vue component seems to be missing the definition of $v for Vuelidate

I've been struggling to resolve this issue. The error message I am encountering pertains to a Vue component that is utilizing the Vuelidate library for form validation. Do you have any insights on what might be causing this error? Uncaught TypeError: ...

The JSX snippet accurately displays the expected value on some pages, but displays an incorrect value on other pages

{_id === friendId || <IconButton onClick={() => patchFriend() } sx={{ backgroundColor: primaryLight, p: "0.6rem" }} > {isFriend ? ( <PersonRemoveOutlined sx={{ color: primaryDark }} /> ...

Accepting PHP multidimensional array through ajax

My PHP code includes a script to open a database, fetch data, and encode it into JSON format. include_once($preUrl . "openDatabase.php"); $sql = 'SELECT * FROM dish'; $query = mysqli_query($con,$sql); $nRows = mysqli_num_rows($query); if($nRow ...

The flag will never turn true; it's stuck in the false position

Currently, I am in the process of developing a custom hook to store data on a server. To mimic the server call, I have implemented a simple setTimeout function that changes the value of the data creation flag to true after 2 seconds. I have a specific fun ...

Redirect the URL in react-snap to a new URL with a forward slash at the end

I recently implemented react-snap to generate static HTML for my website. However, I encountered some SEO issues after implementing react-snap. The old URLs (which were without slashes) are now redirecting to new URLs with slashes. For example: This URL: ...

How do I access Google Chrome and perform a Google image search within a Node.js/Electron program?

I am currently working on incorporating Google Image search into my Electron-based photo application. My goal is to have users click on a button labeled "Search Google for Image," which will then open up Chrome (if installed) with the local image file in ...

Uploading Files within Angular FormArray

I am facing an issue with my formArray which contains file upload inputs in each element. Whenever I upload an image in one input, it changes the values of other file inputs in different rows. https://i.stack.imgur.com/3haZW.png Current Behavior: Uploadi ...

Tips for saving and editing a file with JavaScript

I need a website visitor to input text into a text area on my site. When they submit the form, I want the entered text to be saved in a .txt file located in the same directory as the current web page. I am unsure of how this can be accomplished, and if i ...

Exporting JSON data as an Excel file in AngularJS, including the option to customize the fonts used for the

Currently, I am working on a project where I need to convert JSON data to an Excel file using JavaScript in combination with AngularJS. So far, I have successfully converted the JSON data to CSV format. However, I faced issues with maintaining the font s ...

Enhancing Material UI icons with a sleek linear gradient

Despite following the instructions in this post Attempting to incorporate a linear gradient into a MaterialUI icon, as per a comment's recommendation, I am unable to get it to work. I experimented with the idea that the icons could be considered text ...

I am struggling to make php redirect work using onclick() function

My PHP button is not redirecting properly. Assuming the page destination is correct, is there anything else that could be causing this issue? echo "<button id=\"create\" onclick=\"location.href('/team/teams.php?op=create');&bso ...

Is it possible to retrieve text from various iframes using the rangy library?

Here's a question that follows up on a problem I've been having with grabbing selected text from iframes using rangy. The code works well for the first iframe, but when I try to grab elements from multiple iframes using the same button, it doesn& ...

Sass Alert: The mixin called roboto-family is missing from the stylesheet. Trace: Problem detected in src/pages/forms/forms.scss at

Greetings, I am diving into the world of Ionic for the first time. Recently, I embarked on a new project in Ionic and decided to integrate a theme. To do so, I copied an .html file, an .scss file, and also created a .ts file. Forms.html <!DOCTYPE html ...

The fusion of Combining Forms, JSON, AJAX, PHP, and Google graphs

I'm completely new to integrating JScript, Ajax, and Google Graphs. Although I've managed to get each of them working separately, combining them has proven to be quite challenging. Google Graph Part of my code (Form is inactive here): <html& ...