1

I have a simple for loop like this:

  private async loadImages(urls: string[]): Promise<void> {
    await Promise.all(urls.map(async (url) => {
      this.loadImage(url);
    }));
  }

Where loadImage prototype is:

private async loadImage(url: string): Promise<Image>

If my understanding of this post is correct, the loadImages function calls loadImage(s) in parallel. However looking at chome network tab here is what I see (calls appears to be done cascade, as if one was waiting for the previous one):

enter image description here

Are there tool to debug what is going on wrong ? is there a simple check to verify if loadImage calls are indeed in 'parallel' ?

malat
  • 12,152
  • 13
  • 89
  • 158

2 Answers2

0

Everything is doing what it's supposed to do. Since javascript is single threaded you cannot execute something in parallel. Nevertheless, promises are asynchronous but this is not the same as parallel. If you want to learn more about how asynchronous function work in a single thread I can suggest this post.

So Promise.all doesn't spawn new threads like you might have thought. Instead, it returns a single promise that resolves when all of the input's promises resolve, or rejects as soon as one of the input's promise rejects. Here you can read more about the Promise.all function.


After ritaj commented on my answer I made a test to be sure:

await Promise.all(
    Array(200).fill(0).map(() => fetch('https://ghibliapi.herokuapp.com/films'))
);

I watched the thread count of my browser while running this code (sorry herokuapp for 200 requests...). It stayed the same. Therefore the requests must have been executed sequentially.

nimalu
  • 29
  • 2
  • 1
    None of this is true. Network requests are not executed on the main thread and they can definitely be run in parallel. – Roberto Zvjerković Mar 01 '21 at 12:23
  • [This](https://stackoverflow.com/questions/8963209/does-async-programming-mean-multi-threading) question has a good answer about **async**, **threads** and so on. – nimalu Mar 01 '21 at 12:24
  • 3
    @ritaj are you sure? As far as I know, they are indeed concurrent, but that's not parallel. Why else would the network events look like a "waterfall"? – nimalu Mar 01 '21 at 12:36
0

You should compare it with:

private async loadImages(urls: string[]): Promise<void> {
  for(url of urls) {
    await this.loadImage(url);
  }
}

that call really does wait for each promise to succeed before starting the next and should be far worse in total time to completion.

In a more general sense: You cannot gain infinite concurrency. JavaScript is single-threaded and starting 100 requests in parallel may in fact make your code slower as you have too many requests open at the same time. (You may end up doing a denial of service attack without even realizing it.)

It may make sense to chunk your urls in a set of 5 or 10 urls, and do a Promise.all on each chunk. (Or use a Promise libary where you can set the concurrency like Bluebird).


I did a testcase but bear in mind that I was using nodejs and not a browser so your mileage may vary. I compared the all in parallel, the iterative and a chunked approach. The all-in-parallel was by far the fastest:

  • parallel: 345.985ms
  • chunked: 1633.872ms
  • iterative: 6681.045ms

Here's my code:

import fetch from 'node-fetch';

const parallel = async (urls: string[]): Promise<void> => {
    await Promise.all(
        urls.map(async (url) => {
            return fetch(url);
        })
    );
}

const chunk = <T>(array: T[], size: number = 5): T[][] =>
    Array.from({length: Math.ceil(array.length / size)}, (value, index) => array.slice(index * size, index * size + size));


const chunked = async (urls: string[]): Promise<void> => {
    const chunkedUrls = chunk(urls);


    for (const chunk of chunkedUrls) {
        await Promise.all(chunk.map(url => fetch(url)));
    }
}


const iterative = async (urls: string[]): Promise<void> => {
    for (const url of urls) {
        await fetch(url);
    }
}

const compare = async () => {
    const urls = [...Array(100)].map(() => `http://placekitten.com`);

    console.time('parallel');
    await parallel(urls);
    console.timeEnd('parallel');

    console.time('chunked');
    await chunked(urls);
    console.timeEnd('chunked');

    console.time('iterative');
    await iterative(urls);
    console.timeEnd('iterative')

    return 'completed';
}

compare()
    .then(console.log)
    .catch(console.error);
k0pernikus
  • 60,309
  • 67
  • 216
  • 347