Ignoring setTimeout() function within forEach() in Angular leads to issues

I am currently working on the frontend development of a Web Application using Angular. My task involves saving data from an array into a database by making repeated API calls until all the array data is processed. I implemented the use of setTimeout() in the code snippet below to ensure that each API call finishes processing an item from the array before moving on to the next. However, it seems like the setTimeout() function is not functioning as expected and I'm unsure why this is happening. Any suggestions or insights would be greatly appreciated. Thank you in advance!

this.userEntitiesRoles.forEach(newRole => {
            this.user.entity = newRole.entity;
            this.user.role = newRole.role;
            setTimeout(() => {
              this.usersService.addUser(this.user, this.lang).subscribe({
                next: (): void => {},
                error: (err): void => {
                  if(err.status != 401) {
                    var displayMessage = this.translate.instant('ERRORS.GENERAL_ERROR').toUpperCase().concat(' \r\n').concat(err.message);
                    this.notify.error(displayMessage);
                  }
                }
              });
            }, 1000);
});
addUser(user: IUser, lang: string): Observable<IUser> {
    let payload = new FormData();
    payload.append('emailID', `${user.email}`);
    return this.http.post<IUser>(`${environment.SERVER_ROOT}/add?lang=${lang}`, payload)
    .pipe(
      catchError(this.handleError)
    );
}

Answer №1

It has been observed that using setTimeout() within a loop does not result in sequential execution of calls.

Imagine the following scenario:

setTimeout(() => console.log('#1'), 1000);
setTimeout(() => console.log('#2'), 1000);
setTimeout(() => console.log('#3'), 1000);

In this case, all three statements will be executed after a 1000ms delay.

An alternative approach is to nest the timeouts like so:

setTimeout(() => { 
    console.log('#1'); 
    setTimeout(() => {
        console.log('#2');
        setTimeout(() => console.log('#3'), 1000);        
    }, 1000);
}, 1000);

However, this nesting method can be cumbersome and unwieldy.

The current method being used enforces a consistent 1000ms delay every time, regardless of actual API call times (resulting in unnecessary waiting) or delays longer than expected (leading to overlapping calls).

A more efficient approach would be to execute the calls consecutively as soon as they are finished.

This can be achieved using concat.

// Create an array of observable calls to 'addUser'
const roleUpdates = this.userEntitiesRoles.map(
  ({entity, role}) => this.usersService.addUser({ ...this.user, entity, role }, this.lang)
);

concat(...roleUpdates).subscribe({
  next: (): void => {},
  error: err => {
    if(err.status != 401) {
      const displayMessage = this.translate.instant('ERRORS.GENERAL_ERROR').toUpperCase().concat(' \r\n').concat(err.message);
      this.notify.error(displayMessage);
    }
  }
});

Answer №2

The reason why it is being overlooked is due to subscribing to an observable within the setTimeout function

To resolve this issue, consider utilizing promises. Modify your userService function responsible for adding a user to return a promise

addUser(user: IUser, lang: string): Promise<IUser> {
   let payload = new FormData();
    payload.append('emailID', `${user.email}`);
    return this.http.post<IUser>(`${environment.SERVER_ROOT}/add?lang=${lang}`)
    .toPromise()

}

Subsequently, implement async await to ensure each user addition operation is completed effectively

this.userEntitiesRoles.forEach(async newRole => {
     this.user.entity = newRole.entity;
     this.user.role = newRole.role;
     await this.usersService.addUser(this.user, this.lang)
});

Furthermore, enclose your code within a try-catch block to handle errors proficiently

this.userEntitiesRoles.forEach(async newRole => {
     this.user.entity = newRole.entity;
     this.user.role = newRole.role;
     try{
        await this.usersService.addUser(this.user, this.lang)
     }catch(err) {
         if(err.status != 401) {
            var displayMessage = this.translate.instant('ERRORS.GENERAL_ERROR').toUpperCase().concat(' \r\n').concat(err.message);
            this.notify.error(displayMessage);
          }
     }
});

Answer №3

One alternative approach is to utilize rxjs concat for a more efficient solution. By creating an array of requests and subscribing to them simultaneously, concat guarantees sequential execution, waiting until the previous request is completed. Relying on timeouts can lead to issues, especially in slower connections where 1 second might not suffice.

import { concat } from 'rxjs';

const requests = [];
this.userEntitiesRoles.forEach(newRole => {
  requests.push(this.usersService.addUser(newRole.entity, newRole.role));
});

concat(requests).subscribe(
  res => {//...}, 
  err => {//..}
);

To learn more about this method, refer to concat documentation

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

Extracting Object Properties from Arrays in TypeScript

Code Snippet: interface Human { name: string; age: number; } interface Pet { name: string; type: string; } If I have an array of Human, how can I get an array of Pet's type. For instance: Is there a built-in way to achieve this in typescr ...

Steps for displaying detailed information about a single product on an Ecommerce page

Currently in the process of developing my Ecommerce project, I have successfully created a product grid with links to each specific product. However, I am facing an issue where I am unable to view the data of each individual item. Below is the code for my ...

Executing a method of another component in Angular2

In my Angular2 application, I have created a Header component that is rendered within the main App component. Now, I am faced with the challenge of placing a submit button from another Form component into the Header. How can I achieve this? I find myself ...

The type 'MenuOptions[]' cannot be assigned to type 'empty[]'

Even after numerous attempts, I am still grappling with TypeScript problems. Currently, I am at a loss on how to resolve this particular issue, despite all the research I have conducted. The code snippet below is what I am working with, but I am struggling ...

Display the HTML tag inside a cell of a mat-table

I am currently exploring options to display an HTML tag within a cell of a mat-table in Angular. My goal is to show a colored circle next to the cell value. I have searched online for solutions, but haven't found one that works. If anyone has any insi ...

The Next.js API has a mysterious parameter that remains undefined

I currently have a component implemented import React, { useEffect } from "react"; import styles from "../styles/success.module.css"; import { useRouter } from "next/router"; import axios from "axios"; const Success ...

Connecting to Multiple Databases in NestJS with MySQL Without Defining Entities

If you're interested in learning about connecting to MySQL using TypeORM and defining Entities, the NestJS documentation has all the information you need. In a situation where you have to connect to a MySQL server with multiple databases and need to ...

Leverage the retrieved configuration within the forRoot function

Currently working on an Angular app that uses @ngx-translate. In my implementation, I am using TranslateModule.forRoot(...) to set up a TranslateLoader: @NgModule({ imports: [ TranslateModule.forRoot({ loader: { provide: TranslateLoade ...

What is the best way to transfer a @ContentChild attribute to a fairy tale?

Is there a way to transfer an attribute from a component with a @ContentChild decorator to a story? Below is the code for the container component: @Component({ selector: 'app-header', templateUrl: './header.component.html', style ...

Develop a cutting-edge TypeScript library that allows for seamless resolution of optional dependencies by the application

One of my recent projects involved creating a library that I published to a private npm repository. This library consisted of various utilities and had dependencies on other libraries, such as @aws-sdk/client-lambda. However, not all applications utilizin ...

Refining strings to enum keys in TypeScript

Is there a method to refine a string to match an enum key in TypeScript without needing to re-cast it? enum SupportedShapes { circle = 'circle', triangle = 'triangle', square = 'square', } declare const square: string; ...

Error encountered when trying to update tree structure in TypeScript with new data due to incorrect array length validation

I have encountered an issue with my tree data structure in TypeScript. After running the updateInputArray(chatTree); function, I am getting an “invalid array length” error at the line totalArray.push(iteratorNode.data);. Furthermore, the browser freeze ...

Angular application automatically adding 'localhost' before the backend API endpoint

After migrating my backend to AWS, the backend URL is functioning correctly in Postman. However, when I use the backend URL in an Angular service, 'localhost' is being added to the front of it. How can I resolve this issue? Backend URL: api.an ...

Error: the variable is not defined in the "onclick" event

I'm in the process of creating several buttons, each linked to a distinct modal element. I want to achieve this by using their id. However, I'm facing difficulties when trying to reference the variable from the typescript file. Although I don&ap ...

Utilize a dynamically defined union type to create a versatile callback function

I'm currently working on creating a message subscription function. A basic version without types is shown below: function createMessage(message) { postMessage(message) } function addSubscriber(messageType, callback) { handleNewMessage(message =&g ...

What is the method to dynamically add an error to a FormGroup control using programming?

I am working with a dynamic FormGroup and I need to add an error to a form control programmatically. However, the current method I know of replaces any existing errors as shown below: this.userForm.controls.username.setErrors({ 'exists': &apos ...

Typeorm stored procedure that returns a JSON response

Whenever I execute the stored procedure defined in a Microsoft SQL database using TypeORM as shown below: const result=await conn.query('exec Spname @0,@1',[inp1val,inp2val]); I receive a response from the database, but it comes with an addition ...

Attempting to run the command "npx typescript --init" resulted in an error message stating "npm ERR! could not determine executable to run."

What could be the reason behind the error message npm ERR! could not determine executable to run? Currently, I am attempting to set up a basic Node.js application using TypeScript and Yarn. Yarn is a tool that I am not very familiar with. These are the c ...

Challenges Faced with Implementing Active Reports in Angular 9

After following all the necessary steps outlined in this website to integrate Active Reports with Angular 9 (), I encountered an error when trying to compile my app: ERROR in The target entry-point "@grapecity/activereports-angular" has missing dependen ...

Angular Fusion: Delay execution of ngAfterViewInit until data is received from API call in ngOnInit

I'm facing an issue with my code where the API call in ngOnInit is not waiting for the data to be returned before moving on to ngAfterViewInit. I need it to wait because I am performing operations on that data in ngAfterViewInit, but currently, it&apo ...