-1

I'm using Angular 9. I invoke an HTTP endpoint a number of times, using the loop below ...

  getImage(id) {
    let api = `${this.endpoint}/my-image?id=${id}`;
    return this.http
      .get(api, {headers: this.headersMultiPartForm, responseType: "blob"})
      .pipe(retry(this.retry), catchError(this.handleError));
  }

...

    results.forEach(( componentarray, key ) => {
      const idx = key + startingIndex;
      if (!this.data[idx].url) {
        ...
            this.myService.getImage(imageId).subscribe(data => {
                this.createImageFromBlob(data, index);
            }, () => {
            this.isImageLoadingArr[index] = false;
            });
      }
    });

This spawns a number of OPTIONS and GET requests. I would expect the requests to be launched (basically) at the same time, even though the server may take varying lengths to respond to each. However, in my network settings, I'm not finding this. It seems some requests are blocked and waiting for others. Notice these consecutive requests

enter image description here

enter image description here

enter image description here

There is a 1.5 - 2 second delay between some consecutive requests. This seems to happen across different browsers. Is there a concurrency setting in Angular that I can adjust to prevent this blocking from occurring?

Dave
  • 15,639
  • 133
  • 442
  • 830

2 Answers2

0

Each browser has a hard limit on max number of simultaneous connections to a given domain. See here for more info. For instance, Chromium has a max limit 6 simultaneous connections per domain. See this comment from a Chromium engineer for a little more info.

AFAIK, this limit is fixed in Chromium (by extension Chrome) to 6. In FF it could be increased in about:config page by adjusting the network.http.max-persistent-connections-per-server property.

Apart from it, triggering multiple subscriptions from a loop isn't advised unless you know what you're doing. Instead you could use RxJS forkJoin to trigger requests in parallel.

import { forkJoin, NEVER } from 'rxjs';
import { tap } from 'rxjs/operators';

forkJoin(results.map((componentarray, key) => {
  if (!this.data[idx].url) {
    ...
    return this.myService.getImage(imageId).pipe(
      tap({
        next: data => this.createImageFromBlob(data, index),
        error: _ => this.isImageLoadingArr[index] = false
      })
    );
  }
  return NEVER;   // <-- never emit if condition `!this.data[idx].url` is false
})).subscribe();

This will trigger all the requests in parallel and might lead to the same issue again. In that case you could sequentially buffer a specific number of requests and trigger them in parallel using RxJS from function and bufferCount and concatMap operators.

import { from, forkJoin, NEVER } from 'rxjs';
import { bufferCount, concatMap, tap } from 'rxjs/operators';

from(results.map((componentarray, key) => {
  if (!this.data[idx].url) {
    ...
    return this.myService.getImage(imageId).pipe(
      tap({
        next: data => this.createImageFromBlob(data, index),
        error: _ => this.isImageLoadingArr[index] = false
      })
    );
  }
  return NEVER;       // <-- never emit if condition `!this.data[idx].url` is false
})).pipe(
  bufferCount(4),     // <-- max number of parallel requests at any given time
  concatMap(buffer => forkJoin(buffer))
).subscribe();
ruth
  • 29,535
  • 4
  • 30
  • 57
  • So I understand what is happening here, with "forkJoin", I would not see the results until all of the events in the batch had completed, is that right? – Dave Nov 13 '20 at 21:48
  • That depends on where the results are processed. If the results are processed inside the subscriptions then yes. Because `forkJoin` won't emit until all the observables are complete. However, here the results are piped in using `tap` to each source observable individually. So each emission would immediately trigger it's respective `this.createImageFromBlob()` function even as other requests are in progress. – ruth Nov 13 '20 at 21:52
0

What I discovered was my server (Azure Python functions application), was using HTTP 1.1. When I configured the app to use HTTP/2

enter image description here

, the browser sent all requests concurrently without having to make any additional enhancements to the Angular app.

Dave
  • 15,639
  • 133
  • 442
  • 830