Angular's Recursive Issue with the Execution Order of RxJS Observables

I am currently experiencing an issue with the execution order of recursive RxJS Observables within an Angular application. Specifically, I have a service called RefreshReportService that handles the refreshing of reports. The refreshreport method is intended to refresh a report and its nested reports recursively.

The problem arises when the recursive calls appear to be running in parallel rather than waiting for the previous one to complete. Below is a simplified version of the code:

refreshreport(report_name: string): Observable<any> {
    let operations_list: any = [];
    let primary_key: string = "";

    return new Observable((observer) => {
        this.ifreportisfromotherreports(report_name, (nestedreports: any[]) => {
            if (nestedreports) {
                const recursiveCalls = nestedreports.map((nested_report_name) => {
                    return this.refreshreport(nested_report_name);
                });

                from(recursiveCalls).pipe(
                    concatMap((recursiveCall) => recursiveCall)
                ).subscribe({
                    next: (response: any) => { console.log(response); },
                    error: (err) => { console.log(err); },
                    complete: () => {
                        // Proceed with remaining logic after recursive calls
                        observer.complete();
                    }
                });
            } 
            const token = localStorage.getItem('auth-token');
            const headers = new HttpHeaders({
                'Authorization': `token ${token}`
            });

            const formData = new FormData();
            formData.append('report_name', report_name);

            this.http.post('http://127.0.0.1:8000/getreportoperationslist', formData, { headers, withCredentials: true }).subscribe({
                next: (response: any) => {
                    operations_list = response.operations_list;
                    primary_key = response.primary_key;
                },
                error: (err) => {
                    console.log(err);
                },
                complete: () => {
                    const updateapicalls: Observable<any>[] = [];

                    operations_list.forEach((operation: any) => {
                        updateapicalls.push(this.createRequestObservable(operation));
                    });

                    from(updateapicalls).pipe(
                        concatMap((observable) => observable)
                    ).subscribe({
                        next: (response: any) => { console.log(response); },
                        error: (err) => { console.log(err); },
                        complete: () => {
                            this.DeleteReport(report_name, primary_key);
                            observer.complete(); // Notify the outer observer that this recursion is complete
                        }
                    });
                }
            });           
        });
    });
}

I am seeking a solution to ensure that the recursive calls run sequentially, awaiting completion before moving on to the next call. Any suggestions on how to achieve this would be greatly appreciated!

https://i.sstatic.net/UlWYd.png

Included below is a snapshot from the networks tab showing the discrepancies in the ordering of function calls. The initial call is accurate, followed by the first recursive function call, but subsequent calls are not in the correct order.

Upon testing the code, I observed that the function calls were not in the expected order. I anticipate the correct sequence of calling should involve the inner functions first before proceeding to the outer ones.

Answer №1

After every API call, the 'complete' event will be triggered. Would you consider using this method instead?

toArray -> make sure that next or complete is executed after all sequential operations!

fetchReportData(report_name: string): Observable<any> {
    let tasks_list: any = [];
    let main_key: string = "";

    return new Observable((observer) => {
        this.checkIfReportIsNested(report_name, (nestedreports: any[]) => {
            if (nestedreports) {
                const recursiveCalls = nestedreports.map((nested_report_name) => {
                    return this.fetchReportData(nested_report_name);
                });

                from(recursiveCalls).pipe(
                    concatMap((recursiveCall) => recursiveCall),
                    toArray(), // -- modification made here!
                ).subscribe({
                    next: (response: any) => { console.log(response); },
                    error: (err) => { console.log(err); },
                    complete: () => {
                        // Continue with other logic after recursive calls
                        observer.complete();
                    }
                });
            } 
            const token = localStorage.getItem('auth-token');
            const headers = new HttpHeaders({
                'Authorization': `token ${token}`
            });

            const formData = new FormData();
            formData.append('report_name', report_name);

            this.http.post('http://127.0.0.1:8000/getreportoperationslist', formData, { headers, withCredentials: true }).subscribe({
                next: (response: any) => {
                    tasks_list = response.tasks_list;
                    main_key = response.main_key;
                },
                error: (err) => {
                    console.log(err);
                },
                complete: () => {
                    const updateRequests: Observable<any>[] = [];

                    tasks_list.forEach((task: any) => {
                        updateRequests.push(this.sendRequest(task));
                    });

                    from(updateRequests).pipe(
                        concatMap((observable) => observable),
                        toArray(), // -- modification made here!
                    ).subscribe({
                        next: (response: any) => { console.log(response); },
                        error: (err) => { console.log(err); },
                        complete: () => {
                            this.DeleteReport(report_name, main_key);
                            observer.complete(); // Notify outer observer of completion
                        }
                    });
                }
            });           
        });
    });
}

Answer №2

The issue arose from not properly awaiting the completion of update API calls, causing control to shift back to the parent function prematurely. Here is the updated code snippet: ` async refreshreports(report_name: string): Promise { // Fetch nested reports (if any) const nestedReports: string[] | undefined = await new Promise((resolve, reject) => { this.ifreportisfromotherreports(report_name, (nestedreports: any[]) => { if (nestedreports) { resolve(nestedreports); } else { resolve(undefined); } }); });

    // Process nested reports
    if (nestedReports) {
      for (const nested_report_name of nestedReports) {
        await this.refreshreports(nested_report_name);
      }
    }
  
    // Obtain operations list and primary key info
    let operationsList: any[] = [];
    let primaryKey: string = "";
  
    const token = localStorage.getItem('auth-token');
    const headers = new HttpHeaders({
      'Authorization': `token ${token}`,
    });
  
    const formData = new FormData();
    formData.append('report_name', report_name);
  
    await this.http
      .post('http://127.0.0.1:8000/getreportoperationslist', formData, { headers, withCredentials: true })
      .toPromise()
      .then((response: any) => {
        operationsList = response.operations_list;
        primaryKey = response.primary_key;
      })
      .catch((err) => {
        console.error(err);
      });
  
    // Generate and execute update API calls
    const updateApiCalls: Observable<any>[] = [];
    operationsList.forEach((operation) => {
      updateApiCalls.push(this.createRequestObservable(operation));
    });
  
    await from(updateApiCalls)
      .pipe(concatMap((observable) => observable))
      .toPromise();
  
    // Remove report after all operations are complete
    await this.DeleteReport(report_name, primaryKey);
  }

`

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

Creating a connection between properties and their values

I am looking to implement a property for data binding on my own terms. For instance, consider the following list of items: [{name='name1', invalid='error.name1'}] Along with another list of errors. errors : any= ['name1': & ...

The synchronization between the JS timer and MySQL database is out of alignment

<script> <!-- var miliseconds = 0; var seconds = 0; document.d.d2.value='0'; function display(){ if (miliseconds >= 9){ miliseconds = 0; seconds += 1; } else miliseconds += 1; document.d.d2.value = seconds + '.' + milisecond ...

The issue of Ng-Route not functioning properly on a Node/Express static server

I need assistance with my app.js file that currently directs all requests to pages/index.html. Now I am attempting to utilize Angular to route user requests for '/#/media' by adding the following code: academy.config(function($routeProvider) { ...

What is the process of setting up a module bundler to organize files by folder using webpack?

I am wondering about restructuring the file system using webpack. Here is how it looks before building: My goal is to achieve this setup after utilizing webpack: Desired structure after build In addition, I aim to incorporate ES6 import and export for ...

pausing the jQuery countdown upon reaching the time limit

Hey there, I've implemented a jQuery countdown timer in my project. You can find the link to it here. Here is the code snippet I am using to initialize the timer: var fiveSeconds = new Date().getTime() + 5000; $('#spanTimer').countdown(fiv ...

Selecting a HEX code from a color picker to configure three.js as a string

Recently, I've been using a color picker from jscolor.com that outputs colors in the format of FFA6A6. The challenge I'm facing is integrating this color output with three.js, which requires the color to be in the format of 0xFFA6A6. As much as I ...

Categorize an array of objects based on a key using JavaScript

I searched extensively for solutions online, but I still can't seem to make it work and I'm unsure why. Here is an array with objects: array =[ { "name":"Alex", "id":0 }, { "age" ...

I am encountering a TypeScript error with URLSearchParams. The object cannot be successfully converted to a string using the toString() method

During the development of my react app with postgres, express, node, and typescript, I ran into an issue while working on the backend code. The problem arises when trying to utilize URLSearchParams. index.js import express from 'express'; import ...

What is the best way to execute the app functions, such as get and post, that have been defined

After creating a file that sets up an express middleware app and defines the app function in a separate file, you may be wondering how to run the app function. In your app.js file: const express = require('express') const cors = require('c ...

Finding the distance between two coordinates using Mapbox is easy once you understand how to utilize

Currently, I am in the process of learning how to utilize the Mapbox API within my node application. One of my objectives is to execute calculations on the backend, particularly obtaining the distance between two sets of coordinates. I'm struggling w ...

Hiding the message

I am in the process of creating a barista small application that consists of three buttons. My goal is to not display any text until one of the three components is clicked by the user. I believe I am very close to achieving this, but I have been unable to ...

Protecting against CSRF and XSS attacks in Single page applications can be achieved without relying on cookies

We have a unique setup on our mobile website where it functions as a single page application using cfmanifest to load cache. However, we are facing an issue after login when users navigate between 5 to 10 pages and need to submit confidential data through ...

The sort icon in PrimeNG TurboTable is not displayed when the default sorting option is activated

When the default sorting option is enabled on PrimeNG's TurboTable, the sort icon is not initially visible upon loading. However, the column header is styled as intended and the data is sorted correctly. The sort icon only appears when I manually clic ...

Express JS: The requested resource does not have the 'Access-Control-Allow-Origin' header

Currently, I am encountering an issue with my API running on a server and the front-end client attempting to retrieve data from it. Previously, I successfully resolved the cross-domain problem, but now it seems like something has changed as I am receiving ...

Is it possible for me to develop my own custom "then()" function in JavaScript and have it run successfully?

While familiarizing myself with Promises in Javascript through a tutorial, I noticed the use of the then() method in various instances. Upon writing the code below, I stumbled upon the "then()" function within the __proto__ section of the console: const ...

Zod Entry using standard encryption key

I'm attempting to define an object type in zod that looks like this: { default: string, [string]: string, } I've experimented with combining z.object and z.record using z.union, but the validation results are not as expected. const Local ...

The name 'Symbol' cannot be located in the whatwg-fetch type declaration file

Within the dt~whatwg-fetch's d.ts file, there is a declaration that includes the following code snippet. declare class Headers { // other code ommited [Symbol.iterator](): IterableIterator<[string, string]>; } We attempted to create v ...

Sending back numerous information in the catch block

Recently, I was testing out the fetch API and encountered an issue with logging a fetch error. I tried catching the error and logging it within the catch block. Strangely, even when I added additional console.log statements in the catch block, they would ...

How to effectively handle null in Typescript when accessing types with index signatures unsafely

Why am I getting an error that test might be potentially undefined even though I've enabled strictNullCheck in my tsconfig.json file? (I'm unsure of the keys beforehand) const a: Record<string, {value: string}> = {} a["test"].va ...

Changing the name of a checkbox label using jQuery and JavaScript

I am struggling with dynamically creating checkboxes and setting the label names correctly. Setting the inner html value didn't work for me. What is the proper way to achieve this? source: <!DOCTYPE html> <html> <head> ...