0

The problem

While working with a restful API, I had to make multiple requests to retrieve data around a single search. The problem that I seem to be facing is that as the results are returned from a large database, some promises take FOREVER to resolve.

Current solution

Currently I make all the requests in a loop while adding the promises to an array and then using await Promise.all() to wait for them to be resolved but this makes loading times > 30 seconds at times even when the earliest promise resolved within a few seconds.
I am looking for a way that I can 'Lazy Load' the results in. I do have access to the restful server so any changes in either the front-end or back-end would help however I would prefer the changes to be at the front end.

Edit 1

My bad I didn't put any references to the code that I am currently using. For reference here is what I am currently doing
async function retrieve_data() {
    let request_urls = [list of api endpoints to fetch]
    let promises = []

    for (let url of request_urls)
        promises.push( fetch(url) )

    await promise.all( promises )

    // Do something with the returned results
}

The solution I want

async function retrieve_data() {
    let request_urls = [list of api endpoints to fetch]
    let promises = []

    for (let url of request_urls)
        promises.push( fetch(url) )

    let results = Promise.some_magic_function()
    // Or server side
    res.write(Promise.some_magic_function()) // If results are received in chunks
    

    // Do something with the returned results
}

Edit 2


So while I did find Promise.any() and Promise.all() to fix half of my problems, I seem unable to comprehend on how to have some function which adds the value of fulfilled promises to an array as soon as they are fulfilled. While these solutions do work for my usecase, I just cant help but think that there must be a better solution.
  • 3
    It would be easier to help you if you showed your actual client code using `Promise,all()`. Questions about code should include the relevant code. – jfriend00 Oct 02 '22 at 16:25
  • 1
    "*I am looking for a way that I can 'Lazy Load' the results in.*" - into what exactly? This depends a lot on what you are doing with those results, as you cannot just "`return`" them. – Bergi Oct 02 '22 at 16:52
  • 2
    What you're describing is not lazy load. You want ASAP load while still getting notified when all the requests are done. – jfriend00 Oct 02 '22 at 17:18
  • What OS are you running on? One of the things that we have found is that the OS, e.g. Windows, blocks >= 2 outbound connections to the same server. So, this is the trap you'll get yourself into if you make concurrent requests via Promises. Several of your promises will be blocked until the Promises cleared. Unfortunately, you will have to rewrite your logic to sequentially access your server. – Stephen Quan Oct 02 '22 at 22:15
  • @StephenQuan - That's a browser thing, not an OS thing. [Details here](https://stackoverflow.com/questions/985431/max-parallel-http-connections-in-a-browser). That's only relevant for HTTP/1.x ([details here](https://stackoverflow.com/questions/36835972/is-the-per-host-connection-limit-raised-with-http-2)). But yes, if the OP is sending lots of requests to the same domain and those are being processed using HTTP 1.x, as of about 6 they'll start being serialized. – T.J. Crowder Oct 03 '22 at 06:15
  • 1
    What do do you want to do with the results as they drip in? Call some kind of callback? – Evert Oct 05 '22 at 18:49
  • "*how to have some function which adds the value of fulfilled promises to an array as soon as they are fulfilled*" - that's trivial. The hard part is *using* the values in that constantly changing array. If you don't know when the array changes, it's useless. – Bergi Oct 05 '22 at 19:34
  • 1
    "*server side: `res.write(Promise.some_magic_function())`*" - there is no magic for this. `res.write` is called only once, with a single value. At that point, the array needs to be full. If you want a streaming response, you'll a) need to adjust your http headers b) adjust your client c) call `res.write` multiple times – Bergi Oct 05 '22 at 19:36
  • @Bergi, yeah that's the thing. This is the first time that I had to encounter a problem like this. Any help would be appreciated. As for working with a constantly changing array, I have that part down. If you could guide me on how to access the array as it is updated through some callback function, that would be appreciated – Muhammad Yahya Warraich Oct 06 '22 at 13:44
  • @Evert, I do want to have a callback function. In my case all that does is append the new results to an array. That array is used t dynamically update the content of the webpage, the order in which the results appear isn't of concern for as long as the new results are appended to the end, they would be added to end of the array and the content on the webpage would also only be changed/updated at the end. Thanks – Muhammad Yahya Warraich Oct 06 '22 at 13:47
  • @MuhammadYahyaWarraich You might want to [edit] your question to show that part that deals with the constantly changing array. But ultimately, the solution simply is to modify the array whenever a promise resolves, and right after the modification call your callback every time. – Bergi Oct 06 '22 at 16:57

2 Answers2

1

You can handle the individual results as you're populating the array for Promise.all, which you can use to know when they've all finished.

It's tricky without any code in the question to work with, but here's a simple example:

const promises = [1, 2, 3, 4, 5].map(async (num) => {
    const result = await doRequest(num);
    console.log(`Immediate processing for "${result}"`);
    return result; // If you're going to use the contents of the array from `Promise.all`'s fulfillment
});
const allResults = await Promise.all(promises);
console.log(`All processing done, all results:`);
for (const result of allResults) {
    console.log(result);
}

Live Example:

const rndDelay = () => new Promise((resolve) => setTimeout(resolve, Math.round(Math.random() * 1000) + 100));

async function doRequest(num) {
    console.log(`Requesting ${num}`);
    await rndDelay();
    return `Result for ${num}`;
}

async function main() {
    const promises = [1, 2, 3, 4, 5].map(async (num) => {
        const result = await doRequest(num);
        console.log(`Immediate processing for "${result}"`);
        return result; // If you're going to use the contents of the array from `Promise.all`'s fulfillment
    });
    const allResults = await Promise.all(promises);
    console.log(`All processing done, all results:`);
    for (const result of allResults) {
        console.log(result);
    }
}

main()
.catch((error) => console.error(error));
.as-console-wrapper {
    max-height: 100% !important;
}

Notice that the callback we give map (in this example) is an async function, so it returns a promise and we can use await within it.

If you can't use an async wrapper where you're creating the promises, that isn't a problem, you can fall back to .then:

const promises = [1, 2, 3, 4, 5].map((num) => {
    return doRequest(num)
        .then((result) => {
            console.log(`Immediate processing for "${result}"`);
            return result; // If you're going to use the contents of the array from `Promise.all`'s fulfillment
        });
});
const allResults = await Promise.all(promises);
console.log(`All processing done, all results:`);
for (const result of allResults) {
    console.log(result);
}

const rndDelay = () => new Promise((resolve) => setTimeout(resolve, Math.round(Math.random() * 1000) + 100));

async function doRequest(num) {
    console.log(`Requesting ${num}`);
    await rndDelay();
    return `Result for ${num}`;
}

async function main() {
    const promises = [1, 2, 3, 4, 5].map((num) => {
        return doRequest(num)
            .then((result) => {
                console.log(`Immediate processing for "${result}"`);
                return result; // If you're going to use the contents of the array from `Promise.all`'s fulfillment
            });
    });
    const allResults = await Promise.all(promises);
    console.log(`All processing done, all results:`);
    for (const result of allResults) {
        console.log(result);
    }
}

main()
.catch((error) => console.error(error));
.as-console-wrapper {
    max-height: 100% !important;
}
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

Extract the requests to different async functions and process them the moment they resolve:

// Request that responds in 2 seconds
async function req1() {
  let response = await http.get(`${serverRoot}/api/req1`);
  // Work on response from req 1. Everything below this line executes after 2'
}

//Request that responds in 30 seconds
async function req2() {
  let response = await http.get(`${serverRoot}/api/req2`);
  // Work on response from req 1. Everything below this line executes after 30'
}

//Request that responds in 10 seconds
async function req3() {
  let response = await http.get(`${serverRoot}/api/req3`);
  // Work on response from req 1. Everything below this line executes after 30'
}

Then you can go ahead and add them all in an array and wait for all of them to finish, in order to, let's say, hide a loading indicator:

loading = true;
//Notice that we have to invoke the functions inside the array
await Promise.all([req1(), req2(), req3()]);
//Everything below this line will run after the longest request is finished, 30 seconds
loading = false;
dimlucas
  • 5,040
  • 7
  • 37
  • 54