Quentin
Quentin

Reputation: 1933

Waiting for the result of several observables in a for loop

I have a problem and I can not find a solution because I do not know RxJs well.

I created a file input where the user must choose an XLSX file (A spreadsheet) to be able to import data into the database. When the user validates his file, several checks are made on the document before insertion to avoid any problem.

The file is scanned and checked line by line. But one of my verifications is a problem. I have to use a database procedure to check the existence of the data in the database. The problem is that my query returns an observer, and so my for loop traverses my entire file even before the result of the request is received. The console shows me 'Import finish' before checking.

I would like to know if it's possible to wait for a result of the subscibe before continuing the loop or to complete the loop and once all the results are obtained, complete the import.

Component:

importObjectives(): void {
    if (this.file) {
        if (["xlsx","xls","csv"].includes(this.file.name.split('.').pop())) {
            if (typeof (FileReader) !== 'undefined') {
                const reader = new FileReader();

                reader.onload = (e: any) => {
                    let u8 = new Uint8Array(e.target.result);
                    let wb: XLSX.WorkBook = XLSX.read(u8, { type: 'array' });
                    let wsname: string = wb.SheetNames[0];
                    let ws: XLSX.WorkSheet = wb.Sheets[wsname];
                    let xlsData = XLSX.utils.sheet_to_json(ws, { header: 1 });

                    xlsData.shift();
                    for(var row of xlsData) {
                        var lineError = xlsData.indexOf(row) + 2;
                        // Other checks...
                        // --- Problem here ---
                        this.storeWallets.checkWallets(this.XLSXObjective, lineError).subscribe(wallet => { ... })
                    }

                    if(this.errors.length > 0) {
                        console.error("Errors:");
                        console.error(this.errors);
                    } else {
                        console.log("Import finish");
                    }
                    this.dialogRef.close();
                };
                reader.readAsArrayBuffer(this.file);
            }
        } else {
            console.error("Incompatible file");
            this.dialogRef.close();
        }
    } else {
        console.error("No file");
        this.dialogRef.close();
    }
}

Store Wallet:

checkWallets(xlsx: XLSXObjective, lineError): Observable<any> {
    const obs = this.walletService.checkWallets(xlsx).pipe(share());
    obs.subscribe(wallets => { ... });
    return obs;
}

Service Wallet:

checkWallets(xlsx: XLSXObjective): Observable<any> {
    var customError: string = 'ERREUR';
    return this.http.put<Wallet[]>('/api/wallet/CheckWallets', xlsx)
      .pipe(catchError(this.error.handleError<any>(customError)));
}

Thank you for your help and sorry for my english.

Upvotes: 0

Views: 162

Answers (2)

Quentin
Quentin

Reputation: 1933

My solution:

private observables$ = [];

importObjectives(): void {
    if (this.file) {
        if (["xlsx","xls","csv"].includes(this.file.name.split('.').pop())) {
            if (typeof (FileReader) !== 'undefined') {
                const reader = new FileReader();

                reader.onload = (e: any) => {
                    let u8 = new Uint8Array(e.target.result);
                    let wb: XLSX.WorkBook = XLSX.read(u8, { type: 'array' });
                    let wsname: string = wb.SheetNames[0];
                    let ws: XLSX.WorkSheet = wb.Sheets[wsname];
                    let xlsData = XLSX.utils.sheet_to_json(ws, { header: 1 });

                    xlsData.shift();
                    for(var row of xlsData) {
                        var lineError = xlsData.indexOf(row) + 2;
                        // Other checks...
                        this.observables$.push(this.storeWallets.checkWallets(this.XLSXObjective, lineError))
                    }

                    forkJoin(this.observables$).subscribe(results => {
                      if(this.errors.length > 0) {
                          console.error("Errors:");
                          console.error(this.errors);
                      } else {
                          console.log("Import finish");
                      }
                    }
                    this.dialogRef.close();
                };
                reader.readAsArrayBuffer(this.file);
            }
        } else {
            console.error("Incompatible file");
            this.dialogRef.close();
        }
    } else {
        console.error("No file");
        this.dialogRef.close();
    }
}

Upvotes: 0

Jackie
Jackie

Reputation: 336

You can use Observable.forkJoin where you make the loop for observables. That is his purpose.

Observable.forkJoin allows you to wait for all observables to have received data.

I`m not sure what your HTTP calls are returnig, so here is an simple example:

    loadedCharacter: {};
  constructor(private http: HttpClient) { }

  ngOnInit() {
    let character = this.http.get('https://swapi.co/api/people/1');
    let characterHomeworld = this.http.get('http://swapi.co/api/planets/1');

    forkJoin([character, characterHomeworld]).subscribe(results => {
      // results[0] is our character
      // results[1] is our character homeworld
      results[0].homeworld = results[1];
      this.loadedCharacter = results[0];
    });

Upvotes: 1

Related Questions